how to make a person


This is an exercise in making dictionaries with functions, the two important objects to know in Python.

Imagine I want to make a contact list or database of people. I can use a function to represent filling out information for a person, for example

  • First Name

  • Last Name (Surname)

  • Sex

  • Year of Birth


preview

I have these tests by the end of the chapter

 1import datetime
 2import random
 3import src.person
 4import unittest
 5
 6
 7def pick_one(*choices):
 8    return random.choice(choices)
 9
10
11def get_random_name():
12    return pick_one(
13        'jane', 'joe', 'john', 'person',
14        'doe', 'smith', 'blow', 'public',
15    )
16
17
18def calculate_age(year_of_birth):
19    return (
20        datetime.datetime.now().year
21      - year_of_birth
22    )
23
24
25def get_random_year_of_birth():
26    this_year = datetime.datetime.now().year
27    return random.randint(
28        this_year-120, this_year
29    )
30
31
32class TestPerson(unittest.TestCase):
33
34    def test_factory_w_keyword_arguments(self):
35        year_of_birth = get_random_year_of_birth()
36
37        a_person = dict(
38            first_name=get_random_name(),
39            last_name=get_random_name(),
40            sex=pick_one('F', 'M'),
41        )
42
43        reality = src.person.factory(
44            **a_person,
45            year_of_birth=year_of_birth,
46        )
47        my_expectation = dict(
48            **a_person,
49            age=calculate_age(year_of_birth),
50        )
51        self.assertEqual(reality, my_expectation)
52
53    def test_factory_w_optional_arguments(self):
54        first_name = get_random_name()
55        year_of_birth = get_random_year_of_birth()
56
57        reality = src.person.factory(
58            first_name=first_name,
59            year_of_birth=year_of_birth,
60        )
61        my_expectation = dict(
62            first_name=first_name,
63            last_name='doe',
64            sex='M',
65            age=calculate_age(year_of_birth),
66        )
67        self.assertEqual(reality, my_expectation)
68
69    def test_factory_person_says_hello(self):
70        first_name = get_random_name()
71        last_name = get_random_name()
72        sex = pick_one('F', 'M')
73
74        year_of_birth = get_random_year_of_birth()
75        age = calculate_age(year_of_birth)
76
77        a_random_person = src.person.factory(
78            first_name=first_name,
79            last_name=last_name,
80            sex=sex,
81            year_of_birth=year_of_birth,
82        )
83
84        reality = src.person.say_hello(a_random_person)
85        my_expectation = (
86            f'Hi, my name is {first_name} {last_name}'
87            f' and I am {age}'
88        )
89        self.assertEqual(reality, my_expectation)
90
91
92# Exceptions seen
93# AssertionError
94# NameError
95# AttributeError
96# TypeError
97# SyntaxError

start the project

  • I name this project person

  • I open a terminal

  • I use uv to make a directory for the project and initialize it

    uv init person
    

    the terminal shows

    Initialized project `person` at `.../pumping_python/person`
    

    then goes back to the command line.

  • I change directory to the project

    cd person
    

    the terminal shows I am in the person folder

    .../pumping_python/person
    
  • I make a directory for the source code

    mkdir src
    

    the terminal goes back to the command line.

  • I use the mv program to change the name of main.py to person.py and move it to the src folder

    mv main.py src/person.py
    
    Move-Item main.py src/person.py
    

    the terminal goes back to the command line.

  • I make a directory for the tests

    mkdir tests
    

    the terminal goes back to the command line.

  • I make the tests directory a Python package

    Danger

    use 2 underscores (__) before and after init for __init__.py not _init_.py

    touch tests/__init__.py
    
    New-Item tests/__init__.py
    

    the terminal goes back to the command line.

  • I make a Python file for the tests in the tests directory

    touch tests/test_person.py
    
    New-Item tests/test_person.py
    

    the terminal goes back to the command line.

  • I open test_person.py in the editor of the Integrated Development Environment (IDE)

    Tip

    I can open a file from the terminal in the Integrated Development Environment (IDE) with the name of the program and the name of the file. That means if I type this in the terminal

    code tests/test_person.py
    

    Visual Studio Code opens test_person.py in the editor

  • I add the first failing test to test_person.py

    1import unittest
    2
    3
    4class TestPerson(unittest.TestCase):
    5
    6    def test_failure(self):
    7        self.assertFalse(True)
    
  • I go back to the terminal to make a requirements file for the Python packages I need

    echo "pytest" > requirements.txt
    

    the terminal goes back to the command line.

  • I add pytest-watcher to the requirements file

    echo "pytest-watcher" >> requirements.txt
    

    the terminal goes back to the command line.

  • I install the Python packages that I wrote in the requirements file

    uv add --requirement requirements.txt
    

    the terminal shows that it installed the Python packages

  • I add the new files and folders to git for tracking

    git add .
    

    the terminal goes back to the command line.

  • I add a git commit message

    git commit -am 'setup project'
    

    the terminal shows

    [main (root-commit) a0b12c3] setup project
     9 files changed, 148 insertions(+)
     create mode 100644 .gitignore
     create mode 100644 .python-version
     create mode 100644 README.md
     create mode 100644 pyproject.toml
     create mode 100644 requirements.txt
     create mode 100644 src/person.py
     create mode 100644 tests/__init__.py
     create mode 100644 tests/test_person.py
     create mode 100644 uv.lock
    

    then goes back to the command line.

  • I use pytest-watcher to run the tests automatically

    uv run pytest-watcher . --now
    

    the terminal is my friend, and shows AssertionError

    ================================ FAILURES ================================
    ______________________ TestPerson.test_failure ________________________
    
    self = <tests.test_person.TestPerson testMethod=test_failure>
    
        def test_failure(self):
    >       self.assertFalse(True)
    E       AssertionError: True is not false
    
    tests/test_person.py:7: AssertionError
    ======================== short test summary info =========================
    FAILED tests/test_person.py::TestPerson::test_failure - AssertionError: True is not false
    =========================== 1 failed in X.YZs ============================
    

    because True is NOT False

    if the terminal does not show the same error, then check

    • if your tests/__init__.py has two underscores (__) before and after init for __init__.py not _init_.py

    • if you ran echo "pytest-watcher" >> requirements.txt, to add pytest-watcher to the requirements file

    fix those errors and try to run uv run pytest-watcher . --now again

  • I add AssertionError to the list of Exceptions seen in test_person.py in the editor

     4class TestPerson(unittest.TestCase):
     5
     6    def test_failure(self):
     7        self.assertFalse(True)
     8
     9
    10# Exceptions seen
    11# AssertionError
    
  • then I change True to False in the assertion

    7        self.assertFalse(False)
    

    the test passes.


test_factory_w_keyword_arguments


RED: make it fail


  • I change test_failure to test_factory_w_keyword_arguments

     4class TestPerson(unittest.TestCase):
     5
     6    def test_factory_w_keyword_arguments(self):
     7        reality = src.person.factory()
     8        my_expectation = None
     9        self.assertEqual(reality, my_expectation)
    10
    11
    12# Exceptions seen
    13# AssertionError
    

    the terminal is my friend, and shows NameError

    NameError: name 'src' is not defined
    

    because there is no definition for src in test_person.py

  • I add NameError to the list of Exceptions seen

    12# Exceptions seen
    13# AssertionError
    14# NameError
    

GREEN: make it pass


  • I add an import statement for the person module at the top of test_person.py

    1import src.person
    2import unittest
    
  • I add AttributeError to the list of Exceptions seen

    13# Exceptions seen
    14# AssertionError
    15# NameError
    16# AttributeError
    
  • I use the Explorer to open person.py from the src folder in the editor

  • I delete the text, then add a function to person.py

    1def factory():
    2    return None
    

    the test passes.


REFACTOR: make it better


  • I want the function to take a keyword argument called first_name. I add it to the test in test_person.py

     7    def test_factory_w_keyword_arguments(self):
     8        reality = src.person.factory(
     9            first_name='first_name',
    10        )
    11        my_expectation = None
    12        self.assertEqual(reality, my_expectation)
    13
    14
    15# Exceptions seen
    

    the terminal is my friend, and shows TypeError

    TypeError: factory() got
               an unexpected keyword argument 'first_name'
    

    because the function definition for src.person.factory does not allow calling it with inputs (the parentheses are empty) and the test sends 'first_name' as input.

  • I add TypeError to the list of Exceptions seen

    15# Exceptions seen
    16# AssertionError
    17# NameError
    18# AttributeError
    19# TypeError
    
  • I add first_name as an input parameter to the function in person.py

    1def factory(first_name):
    2    return None
    

    the test passes.


  • I want the function to take a keyword argument called last_name. I add it to the test in test_person.py

     7    def test_factory_w_keyword_arguments(self):
     8        reality = src.person.factory(
     9            first_name='first_name',
    10            last_name='last_name',
    11        )
    12        my_expectation = None
    13        self.assertEqual(reality, my_expectation)
    14
    15
    16# Exceptions seen
    

    the terminal is my friend, and shows TypeError

    TypeError: factory() got
               an unexpected keyword argument 'last_name'.
               Did you mean 'first_name'?
    

    because the test called the factory function with a keyword argument (last_name) that is not in the function definition

  • I add last_name to the function definition in person.py

    1def factory(first_name, last_name):
    2    return None
    

    the test passes.


  • I want the function to take a keyword argument called sex. I add it to the test in test_person.py

     7    def test_factory_w_keyword_arguments(self):
     8        reality = src.person.factory(
     9            first_name='first_name',
    10            last_name='last_name',
    11            sex='M',
    12        )
    13        my_expectation = None
    14        self.assertEqual(reality, my_expectation)
    15
    16
    17# Exceptions seen
    

    the terminal is my friend, and shows TypeError

    TypeError: factory() got
               an unexpected keyword argument 'sex'
    

    because the test called the factory function with a keyword argument (sex) that is not in the function definition

  • I add sex as an input parameter to the factory function in person.py

    1def factory(
    2        first_name, last_name,
    3        sex,
    4    ):
    5    return None
    

    the test passes.


  • I want the function to take a keyword argument for year_of_birth. I add it to the test in test_person.py

     7    def test_factory_w_keyword_arguments(self):
     8        reality = src.person.factory(
     9            first_name='first_name',
    10            last_name='last_name',
    11            sex='M',
    12            year_of_birth=2000,
    13        )
    14        my_expectation = None
    15        self.assertEqual(reality, my_expectation)
    16
    17
    18# Exceptions seen
    

    the terminal is my friend, and shows TypeError

    TypeError: factory() got
               an unexpected keyword argument 'year_of_birth'
    

    because the test called the factory function with a keyword argument (year_of_birth) that is not in the function definition

  • I add the name to the function definition in person.py

    1def factory(
    2        first_name, last_name,
    3        sex, year_of_birth,
    4    ):
    5    return None
    

    the test passes.


  • I want the factory function to return a dictionary (any key-value pairs in curly braces { } separated by a comma) as output when it is called. I change my_expectation in test_person.py

     7    def test_factory_w_keyword_arguments(self):
     8        reality = src.person.factory(
     9            first_name='first_name',
    10            last_name='last_name',
    11            sex='M',
    12            year_of_birth=2000,
    13        )
    14        my_expectation = dict()
    15        self.assertEqual(reality, my_expectation)
    16
    17
    18# Exceptions seen
    

    the terminal is my friend, and shows AssertionError

    AssertionError: None != {}
    

    because the function returns None and the assertion expects {}

  • I change None to a dictionary in the return statement in person.py

    1def factory(
    2        first_name, last_name,
    3        sex, year_of_birth,
    4    ):
    5    return {}
    

    the test passes because {} and dict() are two ways to make the empty dictionary.


  • I add a key called first_name to the dictionary for my_expectation, with the same value as what is given in the call to the factory function in test_person.py

     7    def test_factory_w_keyword_arguments(self):
     8        reality = src.person.factory(
     9            first_name='first_name',
    10            last_name='last_name',
    11            sex='M',
    12            year_of_birth=2000,
    13        )
    14        my_expectation = dict(
    15            first_name='first_name',
    16        )
    17        self.assertEqual(reality, my_expectation)
    18
    19
    20# Exceptions seen
    

    the terminal is my friend, and shows AssertionError

    AssertionError: {} != {'first_name': 'first_name'}
    

    because the function returns the empty dictionary and the assertion expects one with first_name as the key

  • I change the return statement to give the test what it wants, in person.py

    1def factory(
    2        first_name, last_name,
    3        sex, year_of_birth,
    4    ):
    5    return {'first_name': 'first_name'}
    

    the test passes.

  • I change the value of first_name to 'jane' to use a real name for reality and my_expectation, in test_person.py

     7    def test_factory_w_keyword_arguments(self):
     8        reality = src.person.factory(
     9            # first_name='first_name',
    10            first_name='jane',
    11            last_name='last_name',
    12            sex='M',
    13            year_of_birth=2000,
    14        )
    15        my_expectation = dict(
    16            # first_name='first_name',
    17            first_name='jane',
    18        )
    19        self.assertEqual(reality, my_expectation)
    20
    21
    22# Exceptions seen
    

    the terminal is my friend, and shows AssertionError

    AssertionError: {'first_name': 'first_name'}
                 != {'first_name': 'jane'}
    

    because I changed the value for first_name in my_expectation

  • I change the value for the first_name key in person.py

    1def factory(
    2        first_name, last_name,
    3        sex, year_of_birth,
    4    ):
    5    # return {'first_name': 'first_name'}
    6    return {'first_name': 'jane'}
    

    the test passes.

  • I typed the value for first_name two times in the test, which means I have to make a change in two places every time I want a different value for it. I add a variable to use to remove the repetition of 'jane' from test_person.py

     7    def test_factory_w_keyword_arguments(self):
     8        first_name = 'jane'
     9
    10        reality = src.person.factory(
    
  • I use the variable to remove repetition of 'jane'

     7    def test_factory_w_keyword_arguments(self):
     8        first_name = 'jane'
     9
    10        reality = src.person.factory(
    11            # first_name='first_name',
    12            # first_name='jane',
    13            first_name=first_name,
    14            last_name='last_name',
    15            sex='M',
    16            year_of_birth=2000,
    17        )
    18        my_expectation = dict(
    19            # first_name='first_name',
    20            # first_name='jane',
    21            first_name=first_name,
    22        )
    23        self.assertEqual(reality, my_expectation)
    24
    25
    26# Exceptions seen
    

    the test is still green. I now only need to change the value of first_name in one place in the test.


  • I add a key called last_name to the dictionary, with the same value as what is given in the call to the factory function in test_person.py

     7    def test_factory_w_keyword_arguments(self):
     8        first_name = 'jane'
     9
    10        reality = src.person.factory(
    11            # first_name='first_name',
    12            # first_name='jane',
    13            first_name=first_name,
    14            last_name='last_name',
    15            sex='M',
    16            year_of_birth=2000,
    17        )
    18        my_expectation = dict(
    19            # first_name='first_name',
    20            # first_name='jane',
    21            first_name=first_name,
    22            last_name='last_name',
    23        )
    24        self.assertEqual(reality, my_expectation)
    25
    26
    27# Exceptions seen
    

    the terminal is my friend, and shows AssertionError

    AssertionError:
        {'first_name': 'jane'}
     != {'first_name': 'jane', 'last_name': 'last_name'}
    
  • I change the return statement in person.py

     1def factory(
     2        first_name, last_name,
     3        sex, year_of_birth,
     4    ):
     5    # return {'first_name': 'first_name'}
     6    # return {'first_name': 'jane'}
     7    return {
     8        'first_name': 'jane',
     9        'last_name': 'last_name',
    10    }
    

    the test passes.

  • I change the value of last_name to 'doe' to use a real name for reality and my_expectation, in test_person.py

     7    def test_factory_w_keyword_arguments(self):
     8        first_name = 'jane'
     9
    10        reality = src.person.factory(
    11            # first_name='first_name',
    12            # first_name='jane',
    13            first_name=first_name,
    14            # last_name='last_name',
    15            last_name='doe',
    16            sex='M',
    17            year_of_birth=2000,
    18        )
    19        my_expectation = dict(
    20            # first_name='first_name',
    21            # first_name='jane',
    22            first_name=first_name,
    23            # last_name='last_name',
    24            last_name='doe',
    25        )
    26        self.assertEqual(reality, my_expectation)
    27
    28
    29# Exceptions seen
    

    the terminal is my friend, and shows AssertionError

    AssertionError:
        {'first_name': 'jane', 'last_name': 'last_name'}
     != {'first_name': 'jane', 'last_name': 'doe'}
    

    because I changed the value for last_name in my_expectation

  • I change the value for the last_name key in person.py

     1def factory(
     2        first_name, last_name,
     3        sex, year_of_birth,
     4    ):
     5    # return {'first_name': 'first_name'}
     6    # return {'first_name': 'jane'}
     7    return {
     8        'first_name': 'jane',
     9        # 'last_name': 'last_name',
    10        'last_name': 'doe',
    11    }
    

    the test passes.

  • I typed the value for last_name two times in the test, which means I have to make a change in two places every time I want a different value for it. I add a variable to use to remove the repetition of 'doe' from test_person.py

     7    def test_factory_w_keyword_arguments(self):
     8        first_name = 'jane'
     9        last_name = 'doe'
    10
    11        reality = src.person.factory(
    
  • I use the variable to remove repetition of 'doe'

     7    def test_factory_w_keyword_arguments(self):
     8        first_name = 'jane'
     9        last_name = 'doe'
    10
    11        reality = src.person.factory(
    12            # first_name='first_name',
    13            # first_name='jane',
    14            first_name=first_name,
    15            # last_name='last_name',
    16            # last_name='doe',
    17            last_name=last_name,
    18            sex='M',
    19            year_of_birth=2000,
    20        )
    21        my_expectation = dict(
    22            # first_name='first_name',
    23            # first_name='jane',
    24            first_name=first_name,
    25            # last_name='last_name',
    26            # last_name='doe',
    27            last_name=last_name,
    28        )
    29        self.assertEqual(reality, my_expectation)
    30
    31
    32# Exceptions seen
    

    the test is still green. I now only need to change the value of first_name in one place in the test.


  • I add a key called sex to the dictionary, with the same value as what is given in the call to the factory function in test_person.py

     7    def test_factory_w_keyword_arguments(self):
     8        first_name = 'jane'
     9        last_name = 'doe'
    10
    11        reality = src.person.factory(
    12            # first_name='first_name',
    13            # first_name='jane',
    14            first_name=first_name,
    15            # last_name='last_name',
    16            # last_name='doe',
    17            last_name=last_name,
    18            sex='M',
    19            year_of_birth=2000,
    20        )
    21        my_expectation = dict(
    22            # first_name='first_name',
    23            # first_name='jane',
    24            first_name=first_name,
    25            # last_name='last_name',
    26            # last_name='doe',
    27            last_name=last_name,
    28            sex='M',
    29        )
    30        self.assertEqual(reality, my_expectation)
    31
    32
    33# Exceptions seen
    

    the terminal is my friend, and shows AssertionError

    AssertionError: {'first_name': 'jane', 'last_name': 'doe'}
                 != {'first_name': 'jane', 'last_name': 'doe',
                     'sex': 'M'}
    
  • I add a new key to the return statement in person.py

     1def factory(
     2        first_name, last_name,
     3        sex, year_of_birth,
     4    ):
     5    # return {'first_name': 'first_name'}
     6    # return {'first_name': 'jane'}
     7    return {
     8        'first_name': 'jane',
     9        # 'last_name': 'last_name',
    10        'last_name': 'doe',
    11        'sex': 'M',
    12    }
    

    the test passes.

  • I change the value of sex to 'F' for reality and my_expectation, in test_person.py

     7    def test_factory_w_keyword_arguments(self):
     8        first_name = 'jane'
     9        last_name = 'doe'
    10
    11        reality = src.person.factory(
    12            # first_name='first_name',
    13            # first_name='jane',
    14            first_name=first_name,
    15            # last_name='last_name',
    16            # last_name='doe',
    17            last_name=last_name,
    18            # sex='M',
    19            sex='F',
    20            year_of_birth=2000,
    21        )
    22        my_expectation = dict(
    23            # first_name='first_name',
    24            # first_name='jane',
    25            first_name=first_name,
    26            # last_name='last_name',
    27            # last_name='doe',
    28            last_name=last_name,
    29            # sex='M',
    30            sex='F',
    31        )
    32        self.assertEqual(reality, my_expectation)
    33
    34
    35# Exceptions seen
    

    the terminal is my friend, and shows AssertionError

    AssertionError:
        {'first_name': 'jane', 'last_name': 'doe', 'sex': 'M'}
     != {'first_name': 'jane', 'last_name': 'doe', 'sex': 'F'}
    

    because I changed the value for sex in my_expectation

  • I change the value for the sex key in person.py

     1def factory(
     2        first_name, last_name,
     3        sex, year_of_birth,
     4    ):
     5    # return {'first_name': 'first_name'}
     6    # return {'first_name': 'jane'}
     7    return {
     8        'first_name': 'jane',
     9        # 'last_name': 'last_name',
    10        'last_name': 'doe',
    11        # 'sex': 'M',
    12        'sex': 'F',
    13    }
    

    the test passes.

  • I typed the value for sex two times in the test, which means I have to make a change in two places every time I want a different value for it. I add a variable to use to remove the repetition of 'F' from test_person.py

     7    def test_factory_w_keyword_arguments(self):
     8        first_name = 'jane'
     9        last_name = 'doe'
    10        sex = 'F'
    11
    12        reality = src.person.factory(
    
  • I use the variable to remove repetition of 'F'

     7    def test_factory_w_keyword_arguments(self):
     8        first_name = 'jane'
     9        last_name = 'doe'
    10        sex = 'F'
    11
    12        reality = src.person.factory(
    13            # first_name='first_name',
    14            # first_name='jane',
    15            first_name=first_name,
    16            # last_name='last_name',
    17            # last_name='doe',
    18            last_name=last_name,
    19            # sex='M',
    20            # sex='F',
    21            sex=sex,
    22            year_of_birth=2000,
    23        )
    24        my_expectation = dict(
    25            # first_name='first_name',
    26            # first_name='jane',
    27            first_name=first_name,
    28            # last_name='last_name',
    29            # last_name='doe',
    30            last_name=last_name,
    31            # sex='M',
    32            # sex='F',
    33            sex=sex,
    34        )
    35        self.assertEqual(reality, my_expectation)
    36
    37
    38# Exceptions seen
    

    the test is still green. I now only need to change the value of sex in one place in the test.


  • I want the factory function to return the age of the person it makes. I add a key to my_expectation

     7    def test_factory_w_keyword_arguments(self):
     8        first_name = 'jane'
     9        last_name = 'doe'
    10        sex = 'F'
    11
    12        reality = src.person.factory(
    13            # first_name='first_name',
    14            # first_name='jane',
    15            first_name=first_name,
    16            # last_name='last_name',
    17            # last_name='doe',
    18            last_name=last_name,
    19            # sex='M',
    20            # sex='F',
    21            sex=sex,
    22            year_of_birth=2000,
    23        )
    24        my_expectation = dict(
    25            # first_name='first_name',
    26            # first_name='jane',
    27            first_name=first_name,
    28            # last_name='last_name',
    29            # last_name='doe',
    30            last_name=last_name,
    31            # sex='M',
    32            # sex='F',
    33            sex=sex,
    34            age=2026-2000,
    35        )
    36        self.assertEqual(reality, my_expectation)
    37
    38
    39# Exceptions seen
    

    the terminal is my friend, and shows AssertionError

    AssertionError:
        {'first_name': 'jane', 'last_name': 'doe', 'sex': 'F'}
     != {'first_name': 'jane', 'last_name': 'doe', 'sex': 'F',
         'age': 26}
    
  • I add a new key to the return statement in person.py

     1def factory(
     2        first_name, last_name,
     3        sex, year_of_birth,
     4    ):
     5    # return {'first_name': 'first_name'}
     6    # return {'first_name': 'jane'}
     7    return {
     8        'first_name': 'jane',
     9        # 'last_name': 'last_name',
    10        'last_name': 'doe',
    11        # 'sex': 'M',
    12        'sex': 'F',
    13        'age': 26,
    14    }
    

    the test passes.

  • I change 2000 to 1996 for reality and my_expectation, in test_person.py

     7    def test_factory_w_keyword_arguments(self):
     8        first_name = 'jane'
     9        last_name = 'doe'
    10        sex = 'F'
    11
    12        reality = src.person.factory(
    13            # first_name='first_name',
    14            # first_name='jane',
    15            first_name=first_name,
    16            # last_name='last_name',
    17            # last_name='doe',
    18            last_name=last_name,
    19            # sex='M',
    20            # sex='F',
    21            sex=sex,
    22            # year_of_birth=2000,
    23            year_of_birth=1996,
    24        )
    25        my_expectation = dict(
    26            # first_name='first_name',
    27            # first_name='jane',
    28            first_name=first_name,
    29            # last_name='last_name',
    30            # last_name='doe',
    31            last_name=last_name,
    32            # sex='M',
    33            # sex='F',
    34            sex=sex,
    35            # age=2026-2000,
    36            age=2026-1996,
    37        )
    38        self.assertEqual(reality, my_expectation)
    39
    40
    41# Exceptions seen
    

    the terminal is my friend, and shows AssertionError

    AssertionError:
        {'first_name': 'jane', 'last_name': 'doe', 'sex': 'F',
         'age': 26}
     != {'first_name': 'jane', 'last_name': 'doe', 'sex': 'F',
         'age': 30}
    

    because I changed 2000 in my_expectation

  • I change the value for the age key in person.py

     1def factory(
     2        first_name, last_name,
     3        sex, year_of_birth,
     4    ):
     5    # return {'first_name': 'first_name'}
     6    # return {'first_name': 'jane'}
     7    return {
     8        'first_name': 'jane',
     9        # 'last_name': 'last_name',
    10        'last_name': 'doe',
    11        # 'sex': 'M',
    12        'sex': 'F',
    13        # 'age': 26,
    14        'age': 30,
    15    }
    

    the test passes.

  • I remove the commented lines

     1def factory(
     2        first_name, last_name,
     3        sex, year_of_birth,
     4    ):
     5    return {
     6        'first_name': 'jane',
     7        'last_name': 'doe',
     8        'sex': 'F',
     9        'age': 30,
    10    }
    
  • I typed the year of birth two times in the test, which means I have to make a change in two places every time I want a different value for it. I add a variable to use to remove the repetition of the year of birth from test_person.py

     7    def test_factory_w_keyword_arguments(self):
     8        first_name = 'jane'
     9        last_name = 'doe'
    10        sex = 'F'
    11        year_of_birth = 1996
    12
    13        reality = src.person.factory(
    
  • I use the variable to remove repetition of the year of birth

     7    def test_factory_w_keyword_arguments(self):
     8        first_name = 'jane'
     9        last_name = 'doe'
    10        sex = 'F'
    11        year_of_birth = 1996
    12
    13        reality = src.person.factory(
    14            # first_name='first_name',
    15            # first_name='jane',
    16            first_name=first_name,
    17            # last_name='last_name',
    18            # last_name='doe',
    19            last_name=last_name,
    20            # sex='M',
    21            # sex='F',
    22            sex=sex,
    23            # year_of_birth=2000,
    24            # year_of_birth=1996,
    25            year_of_birth=year_of_birth,
    26        )
    27        my_expectation = dict(
    28            # first_name='first_name',
    29            # first_name='jane',
    30            first_name=first_name,
    31            # last_name='last_name',
    32            # last_name='doe',
    33            last_name=last_name,
    34            # sex='M',
    35            # sex='F',
    36            sex=sex,
    37            # age=2026-2000,
    38            # age=2026-1996,
    39            age=2026-year_of_birth,
    40        )
    41        self.assertEqual(reality, my_expectation)
    42
    43
    44# Exceptions seen
    

    the test is still green.

  • I remove the commented lines

     7    def test_factory_w_keyword_arguments(self):
     8        first_name = 'jane'
     9        last_name = 'doe'
    10        sex = 'F'
    11        year_of_birth = 1996
    12
    13        reality = src.person.factory(
    14            first_name=first_name,
    15            last_name=last_name,
    16            sex=sex,
    17            year_of_birth=year_of_birth,
    18        )
    19        my_expectation = dict(
    20            first_name=first_name,
    21            last_name=last_name,
    22            sex=sex,
    23            age=2026-year_of_birth,
    24        )
    25        self.assertEqual(reality, my_expectation)
    26
    27
    28# Exceptions seen
    

    I now only need to change the value of sex in one place in the test, though I have a problem with the calculation for the age, it will be wrong if this program is run after 2026.

  • I open a new terminal then change directories to person

    cd person
    
  • I add a git commit message in the other terminal

    git commit -am 'add test_factory_w_keyword_arguments'
    

    the terminal shows a summary of the changes then goes back to the command line.


test factory with random year_of_birth

I want the value of the age to be a calculation based on the current year so that it will always be correct (at least most of the time). I can do that with the datetime module from The Python Standard Library which is used for dates and times

I want to use random values in the test to make sure the factory function can handle different values and always calculates the right age


RED: make it fail


  • I add an import statement for the random module at the top of test_person.py

    1import datetime
    2import random
    3import src.person
    4import unittest
    
  • I use a random integer (a whole number without decimals) for the year_of_birth variable

     9    def test_factory_w_keyword_arguments(self):
    10        first_name = 'jane'
    11        last_name = 'doe'
    12        sex = 'F'
    13        # year_of_birth = 1996
    14        year_of_birth = random.randint(
    15            datetime.datetime.now().year-120,
    16            datetime.datetime.now().year
    17        )
    18
    19        reality = src.person.factory(
    20            first_name=first_name,
    21            last_name=last_name,
    22            sex=sex,
    23            year_of_birth=year_of_birth,
    24        )
    
    • random is the random module

    • random.randint() is a call to the randint method from the random module. Okay, this one does not use the same name again

    • datetime.datetime.now().year gives me this year

    • datetime.datetime.now().year-120 gives me this year minus 120

    • random.randint(datetime.datetime.now().year-120, datetime.datetime.now().year) gives me a random number from 120 years ago, up to and including the current year

    • the terminal is my friend, and shows AssertionError

    AssertionError:
        {'first_name': 'jane', 'last_name': 'doe', 'sex': 'F',
         'age': 30}
     != {'first_name': 'jane', 'last_name': 'doe', 'sex': 'F',
         'age': X}
    

    where X is a random age

    Tip

    Anytime I use ctrl+s (Windows/Linux) or command+s (MacOS) to save the file, the test runs again and I get a new random value for the age key.


GREEN: make it pass


  • I add a calculation for the age with the today method to the return statement in person.py

     1def factory(
     2        first_name, last_name,
     3        sex, year_of_birth,
     4    ):
     5    return {
     6        'first_name': 'jane',
     7        'last_name': 'doe',
     8        'sex': 'F',
     9        # 'age': 30,
    10        'age': (
    11            datetime.datetime.today().year
    12           -year_of_birth
    13        ),
    14    }
    

    the terminal is my friend, and shows NameError

    NameError: name 'datetime' is not defined.
               Did you forget to import 'datetime'
    

    because datetime is not defined in this file

  • I add an import statement for the datetime module at the top of person.py

    1import datetime
    2
    3
    4def factory(
    5        first_name, last_name,
    6        sex, year_of_birth,
    7    ):
    

REFACTOR: make it better


  • I add a variable to use to remove repetition of datetime.datetime.now().year from the test in test_person.py

     9    def test_factory_w_keyword_arguments(self):
    10        first_name = 'jane'
    11        last_name = 'doe'
    12        sex = 'F'
    13        # year_of_birth = 1996
    14        this_year = datetime.datetime.now().year
    15        year_of_birth = random.randint(
    16            datetime.datetime.now().year-120,
    17            datetime.datetime.now().year
    18        )
    
  • I use the variable to remove repetition of datetime.datetime.now().year from the test

     9    def test_factory_w_keyword_arguments(self):
    10        first_name = 'jane'
    11        last_name = 'doe'
    12        sex = 'F'
    13        # year_of_birth = 1996
    14        this_year = datetime.datetime.now().year
    15        year_of_birth = random.randint(
    16            # datetime.datetime.now().year-120,
    17            # datetime.datetime.now().year
    18            this_year-120, this_year
    19        )
    20
    21        reality = src.person.factory(
    22            first_name=first_name,
    23            last_name=last_name,
    24            sex=sex,
    25            year_of_birth=year_of_birth,
    26        )
    27        my_expectation = dict(
    28            first_name=first_name,
    29            last_name=last_name,
    30            sex=sex,
    31            # age=2026-year_of_birth,
    32            # age=(
    33            #     datetime.datetime.now().year
    34            #    -year_of_birth
    35            # ),
    36            age=this_year-year_of_birth,
    37        )
    38        self.assertEqual(reality, my_expectation)
    39
    40
    41# Exceptions seen
    
  • I add a git commit message in the other terminal

    git commit -am 'use random values for age'
    

    the terminal shows a summary of the changes then goes back to the command line.


test factory with random sex


RED: make it fail


  • I go back to the terminal that is running the tests

  • I add randomness to the sex variable in test_person.py

     9    def test_factory_w_keyword_arguments(self):
    10        first_name = 'jane'
    11        last_name = 'doe'
    12        # sex = 'F'
    13        sex = random.choice(('F', 'M'))
    14        # year_of_birth = 1996
    15        this_year = datetime.datetime.now().year
    16        year_of_birth = random.randint(
    17            # datetime.datetime.now().year-120,
    18            # datetime.datetime.now().year
    19            this_year-120, this_year
    20        )
    
  • I use ctrl+s (Windows/Linux) or command+s (MacOS) to run the test a few times and it passes if sex is randomly 'F'.

    If sex is randomly 'M', the terminal is my friend, and shows AssertionError

    AssertionError:
        {'first_name': 'jane', 'last_name': 'doe', 'sex': 'F',
         'age': X}
     != {'first_name': 'jane', 'last_name': 'doe', 'sex': 'M',
         'age': X}
    

    where X is the random age


GREEN: make it pass


  • I add the sex input parameter instead of a value that does not change to the return statement in person.py

     4def factory(
     5        first_name, last_name,
     6        sex, year_of_birth,
     7    ):
     8    return {
     9        'first_name': 'jane',
    10        'last_name': 'doe',
    11        # 'sex': 'F',
    12        'sex': sex,
    13        # 'age': 30,
    14        'age': (
    15            datetime.datetime.today().year
    16           -year_of_birth
    17        ),
    18    }
    

    I use ctrl+s (Windows/Linux) or command+s (MacOS) to run the test a few times and it passes with no more random failures

  • I add a git commit message in the other terminal

    git commit -am 'use random values for sex'
    

    the terminal shows a summary of the changes then goes back to the command line.


test factory with random last name


RED: make it fail


  • I go back to the terminal that is running the tests

  • I use random.choice with the last_name variable in test_person.py

     9    def test_factory_w_keyword_arguments(self):
    10        first_name = 'jane'
    11        # last_name = 'doe'
    12        last_name = random.choice((
    13            'doe', 'smith', 'blow', 'public',
    14        ))
    15        # sex = 'F'
    16        sex = random.choice(('F', 'M'))
    17        # year_of_birth = 1996
    18        this_year = datetime.datetime.now().year
    19        year_of_birth = random.randint(
    20            # datetime.datetime.now().year-120,
    21            # datetime.datetime.now().year
    22            this_year-120, this_year
    23        )
    
  • I use ctrl+s (Windows/Linux) or command+s (MacOS) to run the test a few times and it passes if last_name is 'doe'.

    If last_name is NOT doe, the terminal is my friend, and shows AssertionError

    AssertionError:
        {'first_name': 'jane', 'last_name': Z, 'sex': Y,
         'age': X}
     != {'first_name': 'jane', 'last_name': A, 'sex': Y,
         'age': X}
    

    where Z and A are the different random last names, X is the random age, and Y is the random sex.


GREEN: make it pass


  • I use the last_name input parameter as the value for the 'last_name' key in the return statement in person.py

     4def factory(
     5        first_name, last_name,
     6        sex, year_of_birth,
     7    ):
     8    return {
     9        'first_name': 'jane',
    10        # 'last_name': 'doe',
    11        'last_name': last_name,
    12        # 'sex': 'F',
    13        'sex': sex,
    14        # 'age': 30,
    15        'age': (
    16            datetime.datetime.today().year
    17          -year_of_birth
    18        ),
    19    }
    

    I use ctrl+s (Windows/Linux) or command+s (MacOS) to run the test a few times and it passes with no more random failures

  • I add a git commit message in the other terminal

    git commit -am 'use random values for last_name'
    

    the terminal shows a summary of the changes then goes back to the command line.


test factory with random first name


RED: make it fail


  • I go back to the terminal that is running the tests

  • I add randomness to the first_name variable in test_person.py

     9    def test_factory_w_keyword_arguments(self):
    10        # first_name = 'jane'
    11        first_name = random.choice((
    12            'jane', 'joe', 'john', 'person',
    13        ))
    14        # last_name = 'doe'
    15        last_name = random.choice((
    16            'doe', 'smith', 'blow', 'public',
    17        ))
    18        # sex = 'F'
    19        sex = random.choice(('F', 'M'))
    20        # year_of_birth = 1996
    21        this_year = datetime.datetime.now().year
    22        year_of_birth = random.randint(
    23            # datetime.datetime.now().year-120,
    24            # datetime.datetime.now().year
    25            this_year-120, this_year
    26        )
    

    I use ctrl+s (Windows/Linux) or command+s (MacOS) to run the test a few times and it passes if first_name is 'jane'.

    If first_name is not 'jane' the terminal is my friend, and shows AssertionError

    AssertionError:
        {'first_name': A, 'last_name': Z, 'sex': Y, 'age': X}
     != {'first_name': B, 'last_name': Z, 'sex': Y, 'age': X}
    

    where A and B are the different random first names, X is the random age, Y is the random sex, and Z is the random last name

  • I add the first_name input parameter to the return statement in person.py

     4def factory(
     5        first_name, last_name,
     6        sex, year_of_birth,
     7    ):
     8    return {
     9        # 'first_name': 'jane',
    10        'first_name': first_name,
    11        # 'last_name': 'doe',
    12        'last_name': last_name,
    13        # 'sex': 'F',
    14        'sex': sex,
    15        # 'age': 30,
    16        'age': (
    17            datetime.datetime.today().year
    18           -year_of_birth
    19        ),
    20    }
    

    I use ctrl+s (Windows/Linux) or command+s (MacOS) to run the test a few times and it passes with no more random failures

  • I remove the commented lines

     4def factory(
     5        first_name, last_name,
     6        sex, year_of_birth,
     7    ):
     8    return {
     9        'first_name': first_name,
    10        'last_name': last_name,
    11        'sex': sex,
    12        'age': (
    13            datetime.datetime.today().year
    14           -year_of_birth
    15        ),
    16    }
    
  • I add a git commit message in the other terminal

    git commit -am 'use random values for first_name'
    

    the terminal shows a summary of the changes then goes back to the command line.


extract pick_one function


  • I go back to the terminal that is running the tests

  • I add a function for the calls to the random.choice method with a starred expression like I did in test_w_unknown_arguments, to use to remove repetition of random.choice from the test in test_person.py

     1import datetime
     2import random
     3import src.person
     4import unittest
     5
     6
     7def pick_one(*choices):
     8    return random.choice(choices)
     9
    10
    11class TestPerson(unittest.TestCase):
    12
    13    def test_factory_w_keyword_arguments(self):
    
  • I use the new function to remove repetition of random.choice from the test

    13    def test_factory_w_keyword_arguments(self):
    14        # first_name = 'jane'
    15        # first_name = random.choice((
    16        #     'jane', 'joe', 'john', 'person',
    17        # ))
    18        first_name = pick_one(
    19            'jane', 'joe', 'john', 'person',
    20        )
    21        # last_name = 'doe'
    22        # last_name = random.choice((
    23        #     'doe', 'smith', 'blow', 'public',
    24        # ))
    25        last_name = pick_one(
    26            'doe', 'smith', 'blow', 'public',
    27        )
    28        # sex = 'F'
    29        # sex = random.choice(('F', 'M'))
    30        sex = pick_one('F', 'M')
    31        # year_of_birth = 1996
    32        this_year = datetime.datetime.now().year
    33        year_of_birth = random.randint(
    34            # datetime.datetime.now().year-120,
    35            # datetime.datetime.now().year
    36            this_year-120, this_year
    37        )
    

    the test is still green.

  • I add a git commit message in the other terminal

    git commit -am 'extract pick_one function'
    

    the terminal shows a summary of the changes then goes back to the command line.


test factory with a dictionary


The difference between the call to the factory function and the expected dictionary in the test is, one has a year of birth and the other does a calculation with the year of birth. The other things are the same.

RED: make it fail


  • I go back to the terminal that is running the tests

  • I add a dictionary to use to remove the repeating parts

    13    def test_factory_w_keyword_arguments(self):
    14        # first_name = 'jane'
    15        # first_name = random.choice((
    16        #     'jane', 'joe', 'john', 'person',
    17        # ))
    18        first_name = pick_one(
    19            'jane', 'joe', 'john', 'person',
    20        )
    21        # last_name = 'doe'
    22        # last_name = random.choice((
    23        #     'doe', 'smith', 'blow', 'public',
    24        # ))
    25        last_name = pick_one(
    26            'doe', 'smith', 'blow', 'public',
    27        )
    28        # sex = 'F'
    29        # sex = random.choice(('F', 'M'))
    30        sex = pick_one('F', 'M')
    31        # year_of_birth = 1996
    32
    33        a_person = dict(
    34            first_name=first_name,
    35            last_name=last_name,
    36            sex=sex,
    37        )
    38
    39        this_year = datetime.datetime.now().year
    40        year_of_birth = random.randint(
    41            # datetime.datetime.now().year-120,
    42            # datetime.datetime.now().year
    43            this_year-120, this_year
    44        )
    
  • I use the new variable with a double starred expression to remove the repeating parts

    39        this_year = datetime.datetime.now().year
    40        year_of_birth = random.randint(
    41            # datetime.datetime.now().year-120,
    42            # datetime.datetime.now().year
    43            this_year-120, this_year
    44        )
    45
    46        reality = src.person.factory(
    47            # first_name=first_name,
    48            # last_name=last_name,
    49            # sex=sex,
    50            **a_person,
    51            year_of_birth=year_of_birth,
    52        )
    53        my_expectation = dict(
    54            # first_name=first_name,
    55            # last_name=last_name,
    56            # sex=sex,
    57            **a_person,
    58            # age=2026-year_of_birth,
    59            # age=(
    60            #     datetime.datetime.now().year
    61            #    -year_of_birth
    62            # ),
    63            age=this_year-year_of_birth,
    64        )
    65        self.assertEqual(reality, my_expectation)
    66
    67
    68# Exceptions seen
    

    still green.

  • I use the values of first_name, last_name and the sex variables in the a_person dictionary

    13    def test_factory_w_keyword_arguments(self):
    14        # first_name = 'jane'
    15        # first_name = random.choice((
    16        #     'jane', 'joe', 'john', 'person',
    17        # ))
    18        # first_name = pick_one(
    19        #     'jane', 'joe', 'john', 'person',
    20        # )
    21        # last_name = 'doe'
    22        # last_name = random.choice((
    23        #     'doe', 'smith', 'blow', 'public',
    24        # ))
    25        # last_name = pick_one(
    26        #     'doe', 'smith', 'blow', 'public',
    27        # )
    28        # sex = 'F'
    29        # sex = random.choice(('F', 'M'))
    30        # sex = pick_one('F', 'M')
    31        # year_of_birth = 1996
    32
    33        a_person = dict(
    34            # first_name=first_name,
    35            # last_name=last_name,
    36            # sex=sex,
    37            first_name=pick_one(
    38                'jane', 'joe', 'john', 'person',
    39            ),
    40            last_name=pick_one(
    41                'doe', 'smith', 'blow', 'public',
    42            ),
    43            sex=pick_one('F', 'M'),
    44        )
    

    still green.

  • I make a function with a tuple of all the names for the test to have more random names to pick from

     7def pick_one(*choices):
     8    return random.choice(choices)
     9
    10
    11def get_random_name():
    12    return pick_one(
    13        'jane', 'joe', 'john', 'person',
    14        'doe', 'smith', 'blow', 'public',
    15    )
    16
    17
    18class TestPerson(unittest.TestCase):
    19
    20    def test_factory_w_keyword_arguments(self):
    
  • I use the function in the a_person dictionary

    20    def test_factory_w_keyword_arguments(self):
    21        # first_name = 'jane'
    22        # first_name = random.choice((
    23        #     'jane', 'joe', 'john', 'person',
    24        # ))
    25        # first_name = pick_one(
    26        #     'jane', 'joe', 'john', 'person',
    27        # )
    28        # last_name = 'doe'
    29        # last_name = random.choice((
    30        #     'doe', 'smith', 'blow', 'public',
    31        # ))
    32        # last_name = pick_one(
    33        #     'doe', 'smith', 'blow', 'public',
    34        # )
    35        # sex = 'F'
    36        # sex = random.choice(('F', 'M'))
    37        # sex = pick_one('F', 'M')
    38        # year_of_birth = 1996
    39
    40        a_person = dict(
    41            # first_name=first_name,
    42            # last_name=last_name,
    43            # sex=sex,
    44            # first_name=pick_one(
    45            #     'jane', 'joe', 'john', 'person',
    46            # ),
    47            # last_name=pick_one(
    48            #     'doe', 'smith', 'blow', 'public',
    49            # ),
    50            first_name=get_random_name(),
    51            last_name=get_random_name(),
    52            sex=pick_one('F', 'M'),
    53        )
    

    green.

  • I remove the commented lines

    20    def test_factory_w_keyword_arguments(self):
    21        a_person = dict(
    22            first_name=get_random_name(),
    23            last_name=get_random_name(),
    24            sex=pick_one('F', 'M'),
    25        )
    26
    27        this_year = datetime.datetime.now().year
    28        year_of_birth = random.randint(
    29            this_year-120, this_year
    30        )
    31
    32        reality = src.person.factory(
    33            **a_person,
    34            year_of_birth=year_of_birth,
    35        )
    36        my_expectation = dict(
    37            **a_person,
    38            age=this_year-year_of_birth,
    39        )
    40        self.assertEqual(reality, my_expectation)
    41
    42    def test_factory_w_optional_arguments(self):
    
  • I add a git commit message in the other terminal

    git commit -am 'extract a_person dictionary'
    

    the terminal shows a summary of the changes then goes back to the command line.


test_factory_w_optional_arguments

I want to see what happens when I try to make a person without a value for the last_name argument


RED: make it fail


  • I go back to the terminal that is running the tests

  • I make a copy of test_factory_w_keyword_arguments and paste it below in test_person.py

    32        reality = src.person.factory(
    33            **a_person,
    34            year_of_birth=year_of_birth,
    35        )
    36        my_expectation = dict(
    37            **a_person,
    38            age=this_year-year_of_birth,
    39        )
    40        self.assertEqual(reality, my_expectation)
    41
    42    def test_factory_w_keyword_arguments(self):
    43        a_person = dict(
    44            first_name=get_random_name(),
    45            last_name=get_random_name(),
    46            sex=pick_one('F', 'M'),
    47        )
    48
    49        this_year = datetime.datetime.now().year
    50        year_of_birth = random.randint(
    51            this_year-120, this_year
    52        )
    53
    54        reality = src.person.factory(
    55            **a_person,
    56            year_of_birth=year_of_birth,
    57        )
    58        my_expectation = dict(
    59            **a_person,
    60            age=this_year-year_of_birth,
    61        )
    62        self.assertEqual(reality, my_expectation)
    63
    64
    65# Exceptions seen
    
  • I change the name of the new test to test_factory_w_optional_arguments

    32        reality = src.person.factory(
    33            **a_person,
    34            year_of_birth=year_of_birth,
    35        )
    36        my_expectation = dict(
    37            **a_person,
    38            age=this_year-year_of_birth,
    39        )
    40        self.assertEqual(reality, my_expectation)
    41
    42    def test_factory_w_optional_arguments(self):
    43        a_person = dict(
    44            first_name=get_random_name(),
    45            last_name=get_random_name(),
    46            sex=pick_one('F', 'M'),
    47        )
    
  • I comment out the last_name key-value pair in the a_person dictionary

    42    def test_factory_w_optional_arguments(self):
    43        a_person = dict(
    44            first_name=get_random_name(),
    45            # last_name=get_random_name(),
    46            sex=pick_one('F', 'M'),
    47        )
    

    the terminal is my friend, and shows TypeError

    TypeError: factory() missing 1
               required positional argument: 'last_name'
    

    because this test no longer gives a value for last_name when it calls the factory function, I have to make it a choice.


GREEN: make it pass



REFACTOR: make it better


  • I comment out the sex key in test_factory_w_optional_arguments to see what happens when I call the factory function without it, in test_person.py

    42    def test_factory_w_optional_arguments(self):
    43        a_person = dict(
    44            first_name=get_random_name(),
    45            # last_name=get_random_name(),
    46            # sex=pick_one('F', 'M'),
    47        )
    

    the terminal is my friend, and shows AssertionError

    AssertionError:
        {'first_name': Z, 'last_name': Y, 'sex': None, 'age': X}
     != {'first_name': Z, 'last_name': Y, 'age': X}
    

    the factory function returns a dictionary with a 'sex' key, and the assertion expects a dictionary without that key

  • I add a key-value pair for sex to my_expectation in test_factory_w_optional_arguments

    54        reality = src.person.factory(
    55            **a_person,
    56            year_of_birth=year_of_birth,
    57        )
    58        my_expectation = dict(
    59            **a_person,
    60            last_name='doe',
    61            sex='M',
    62            age=this_year-year_of_birth,
    63        )
    64        self.assertEqual(reality, my_expectation)
    65
    66
    67# Exceptions seen
    

    the terminal is my friend, and shows AssertionError

    AssertionError:
        {'first_name': Y, 'last_name': 'doe',
         'sex': None, 'age': X}
     != {'first_name': Y, 'last_name': 'doe',
         'sex': 'M', 'age': X}
    

    because the factory function returns a dictionary with a value of None for sex and the assertion expects 'M'

  • I change the default value for sex in the factory function in person.py

     4def factory(
     5        first_name, last_name='doe',
     6        sex='M', year_of_birth=None,
     7    ):
     8    return {
     9        'first_name': first_name,
    10        'last_name': last_name,
    11        'sex': sex,
    12        'age': datetime.datetime.now().year-year_of_birth,
    13    }
    

    the test passes because the default value for the sex parameter of the function is 'M'. This means that

    src.person.factory(
        first_name=first_name,
        year_of_birth=year_of_birth,
    )
    

    is the same as

    src.person.factory(
        first_name=first_name,
        year_of_birth=year_of_birth,
        last_name='doe',
        sex='M',
    )
    

    A function uses the default value for a parameter when it is called without the parameter.


  • I no longer need the a_person dictionary in test_factory_w_optional_arguments because it has only one key. I add a variable for first_name

    42    def test_factory_w_optional_arguments(self):
    43        first_name = get_random_name()
    44        a_person = dict(
    45            first_name=get_random_name(),
    46            # last_name=get_random_name(),
    47            # sex=pick_one('F', 'M'),
    48        )
    
  • I use the variable for reality and my_expectation

    55        reality = src.person.factory(
    56            **a_person,
    57            first_name=first_name,
    58            year_of_birth=year_of_birth,
    59        )
    60        my_expectation = dict(
    61            **a_person,
    62            first_name=first_name,
    63            last_name='doe',
    64            sex='M',
    65            age=this_year-year_of_birth,
    66        )
    67        self.assertEqual(reality, my_expectation)
    68
    69
    70# Exceptions seen
    

    the terminal is my friend, and shows TypeError

    TypeError: src.person.factory() got multiple values
               for keyword argument 'first_name'
    

    because the a_person dictionary already has a key called first_name, the call to src.person.factory gets called with the same name two times

  • I comment out **a_person,

    55        reality = src.person.factory(
    56            # **a_person,
    57            first_name=first_name,
    58            year_of_birth=year_of_birth,
    59        )
    60        my_expectation = dict(
    61            # **a_person,
    62            first_name=first_name,
    63            last_name='doe',
    64            sex='M',
    65            age=this_year-year_of_birth,
    66        )
    67        self.assertEqual(reality, my_expectation)
    68
    69
    70# Exceptions seen
    

    I use ctrl+s (Windows/Linux) or command+s (MacOS) to run the test a few times and it passes with no more random failures

  • I remove the commented lines and the a_person dictionary

    42    def test_factory_w_optional_arguments(self):
    43        first_name = get_random_name()
    44
    45        this_year = datetime.datetime.now().year
    46        year_of_birth = random.randint(
    47            this_year-120, this_year
    48        )
    49
    50        reality = src.person.factory(
    51            first_name=first_name,
    52            year_of_birth=year_of_birth,
    53        )
    54        my_expectation = dict(
    55            first_name=first_name,
    56            last_name='doe',
    57            sex='M',
    58            age=this_year-year_of_birth,
    59        )
    60        self.assertEqual(reality, my_expectation)
    61
    62
    63# Exceptions seen
    
  • I add a git commit message in the other terminal

    git commit -am 'add test_factory_w_optional_arguments'
    

    the terminal shows a summary of the changes then goes back to the command line.


test_factory_person_says_hello

I have a function that takes in first_name, last_name, sex and year_of_birth for a person and returns a dictionary with first_name, last_name, sex and age (based on the year_of_birth) as keys.

What if I want the person to say hello, How would I do that? I can write a function that takes in a person (dictionary) and returns a message (string).


RED: make it fail


  • I add a new test to test_person.py

    50        reality = src.person.factory(
    51            first_name=first_name,
    52            year_of_birth=year_of_birth,
    53        )
    54        my_expectation = dict(
    55            first_name=first_name,
    56            last_name='doe',
    57            sex='M',
    58            age=this_year-year_of_birth,
    59        )
    60        self.assertEqual(reality, my_expectation)
    61
    62    def test_factory_person_says_hello(self):
    63        joe = src.person.factory(
    64            first_name='joe',
    65            last_name='blow',
    66            year_of_birth=1996,
    67        )
    68
    69        reality = src.person.say_hello(joe)
    70        my_expectation = None
    71        self.assertEqual(reality, my_expectation)
    72
    73
    74# Exceptions seen
    

    the terminal is my friend, and shows AttributeError

    AttributeError: module 'src.person' has no attribute 'hello'
    

    because person.py does not have a function named say_hello


GREEN: make it pass


  • I add the function to person.py

     4def factory(
     5        first_name, last_name='doe',
     6        sex='M', year_of_birth=None,
     7    ):
     8    return {
     9        'first_name': first_name,
    10        'last_name': last_name,
    11        'sex': sex,
    12        'age': (
    13            datetime.datetime.today().year
    14           -year_of_birth
    15        ),
    16    }
    17
    18
    19def say_hello():
    20    return None
    

    the terminal is my friend, and shows TypeError

    TypeError: hello() takes 0 positional arguments
               but 1 was given
    

    because the definition for say_hello does not allow inputs and the test called the function with a positional argument (person)

  • I add a name to the definition

    19def say_hello(person):
    20    return None
    

    the test passes.


REFACTOR: make it better


I want the say_hello function to return a string for the person (dictionary) I give as input

  • I change my_expectation to an f-string in test_factory_person_says_hello in test_person.py

    62    def test_factory_person_says_hello(self):
    63        joe = src.person.factory(
    64            first_name='joe',
    65            last_name='blow',
    66            year_of_birth=1996,
    67        )
    68
    69        reality = src.person.say_hello(joe)
    70        my_expectation = (
    71            'Hi, my name is joe blow and I am'
    72            f' {datetime.datetime.now().year-1996}'
    73        )
    74        self.assertEqual(reality, my_expectation)
    75
    76
    77# Exceptions seen
    

    the terminal is my friend, and shows AssertionError

    AssertionError: None != 'Hi, my name is joe blow and I am 30'
    
  • I copy the value from the terminal and paste it in the return statement in person.py

    19def say_hello(person):
    20    return 'Hi, my name is joe blow and I am 30'
    

    the test passes.

  • I add an assertion for another person

    62    def test_factory_person_says_hello(self):
    63        joe = src.person.factory(
    64            first_name='joe',
    65            last_name='blow',
    66            year_of_birth=1996,
    67        )
    68
    69        reality = src.person.say_hello(joe)
    70        my_expectation = (
    71            'Hi, my name is joe blow and I am'
    72            f' {datetime.datetime.now().year-1996}'
    73        )
    74        self.assertEqual(reality, my_expectation)
    75
    76        jane = src.person.factory(
    77            first_name='jane',
    78            sex='F',
    79            year_of_birth=1991,
    80        )
    81
    82        reality = src.person.say_hello(jane)
    83        my_expectation = (
    84            'Hi, my name is jane doe and I am'
    85            f' {datetime.datetime.now().year-1991}'
    86        )
    87        self.assertEqual(reality, my_expectation)
    88
    89
    90# Exceptions seen
    

    the terminal is my friend, and shows AssertionError

    AssertionError:
          'Hi, my name is joe blow and I am 30'
       != 'Hi, my name is jane doe and I am 35'
    

    I have to make sure the say_hello function uses the values of the person dictionary to make the message. I can do that with the get method of dictionaries.

  • I change the string to an f-string with the value for the first_name key from the dictionary the say_hello function receives, in person.py

    19def say_hello(person):
    20    first_name = person.get('first_name')
    21
    22    # return 'Hi, my name is joe blow and I am 30'
    23    return f'Hi, my name is {first_name} blow and I am 30'
    

    the terminal is my friend, and shows AssertionError

    AssertionError:
        'Hi, my name is jane blow and I am 30'
     != 'Hi, my name is jane doe and I am 35'
    

    the first names are the same, the last name and ages are different

  • I add the value for the last_name key from the dictionary

    19def say_hello(person):
    20    first_name = person.get('first_name')
    21    last_name = person.get('last_name')
    22
    23    # return 'Hi, my name is joe blow and I am 30'
    24    # return f'Hi, my name is {first_name} blow and I am 30'
    25    return (
    26        f'Hi, my name is {first_name} {last_name}'
    27        f' and I am 30'
    28    )
    

    the terminal is my friend, and shows AssertionError

    AssertionError:
        'Hi, my name is jane doe and I am 30'
     != 'Hi, my name is jane doe and I am 35'
    

    the age is the only thing that is different

  • I add the value for the age key from the dictionary

    19def say_hello(person):
    20    first_name = person.get('first_name')
    21    last_name = person.get('last_name')
    22    age = person.get('age')
    23
    24    # return 'Hi, my name is joe blow and I am 30'
    25    # return f'Hi, my name is {first_name} blow and I am 30'
    26    return (
    27        f'Hi, my name is {first_name} {last_name}'
    28        # f' and I am 30'
    29        f' and I am {age}'
    30    )
    

    the test passes.

  • I remove the commented lines

    19def say_hello(person):
    20    first_name = person.get('first_name')
    21    last_name = person.get('last_name')
    22    age = person.get('age')
    23
    24    return (
    25        f'Hi, my name is {first_name} {last_name}'
    26        f' and I am {age}'
    27    )
    
  • I add an assertion for a new person in test_factory_person_says_hello in test_person.py

     76        jane = src.person.factory(
     77            first_name='jane',
     78            sex='F',
     79            year_of_birth=1991,
     80        )
     81
     82        reality = src.person.say_hello(jane)
     83        my_expectation = (
     84            'Hi, my name is jane doe and I am'
     85            f' {datetime.datetime.now().year-1991}'
     86        )
     87        self.assertEqual(reality, my_expectation)
     88
     89        john = src.person.factory(
     90            first_name='john',
     91            last_name='smith',
     92            year_of_birth=1580,
     93        )
     94
     95        reality = src.person.say_hello(john)
     96        my_expectation = (
     97            'Hi, my name is jane doe and I am'
     98            f'{datetime.datetime.now().year-1991}'
     99        )
    100        self.assertEqual(reality, my_expectation)
    101
    102
    103# Exceptions seen
    

    the terminal is my friend, and shows AssertionError

    AssertionError:
        'Hi, my name is john smith and I am 446'
     != 'Hi, my name is jane doe and I am 35'
    
  • I change my_expectation to match reality

     89        john = src.person.factory(
     90            first_name='john',
     91            last_name='smith',
     92            year_of_birth=1580,
     93        )
     94
     95        reality = src.person.say_hello(john)
     96        my_expectation = (
     97            'Hi, my name is john smith and I am'
     98            f' {datetime.datetime.now().year-1580}'
     99        )
    100        self.assertEqual(reality, my_expectation)
    101
    102
    103# Exceptions seen
    

    the test passes.

  • I add an assertion for one more person

     89        john = src.person.factory(
     90            first_name='john',
     91            last_name='smith',
     92            year_of_birth=1580,
     93        )
     94
     95        reality = src.person.say_hello(john)
     96        my_expectation = (
     97            'Hi, my name is john smith and I am'
     98            f' {datetime.datetime.now().year-1580}'
     99        )
    100        self.assertEqual(reality, my_expectation)
    101
    102        a_person = src.person.factory(
    103            first_name='person',
    104            last_name='public',
    105            year_of_birth=2000,
    106            sex='F',
    107        )
    108
    109        reality = src.person.say_hello(a_person)
    110        my_expectation = (
    111            'Hi, my name is john smith and I am'
    112            f' {datetime.datetime.now().year-1580}'
    113        )
    114        self.assertEqual(reality, my_expectation)
    115
    116
    117# Exceptions seen
    

    the terminal is my friend, and shows AssertionError

    AssertionError:
        'Hi, my name is person public and I am 26'
     != 'Hi, my name is john smith and I am 446'
    
  • I change my_expectation to match reality

    102        a_person = src.person.factory(
    103            first_name='person',
    104            last_name='public',
    105            year_of_birth=2000,
    106            sex='F',
    107        )
    108
    109        reality = src.person.say_hello(a_person)
    110        my_expectation = (
    111            'Hi, my name is person public and I am'
    112            f' {datetime.datetime.now().year-2000}'
    113        )
    114        self.assertEqual(reality, my_expectation)
    115
    116
    117# Exceptions seen
    

    the test passes.


  • I add variables to use them to remove repetition of 'person', 'public', 2000 and the age calculation from the last assertion

     89        john = src.person.factory(
     90            first_name='john',
     91            last_name='smith',
     92            year_of_birth=1580,
     93        )
     94
     95        reality = src.person.say_hello(john)
     96        my_expectation = (
     97            'Hi, my name is john smith and I am'
     98            f' {datetime.datetime.now().year-1580}'
     99        )
    100        self.assertEqual(reality, my_expectation)
    101
    102        first_name = 'person'
    103        last_name = 'public'
    104        year_of_birth = 2000
    105        age = (
    106            datetime.datetime.now().year
    107          - year_of_birth
    108        )
    109
    110        a_person = src.person.factory(
    111            first_name='person',
    112            last_name='public',
    113            year_of_birth=2000,
    114            sex='F',
    115        )
    
  • I use the variables to remove repetition of 'person', 'public', 2000 and the age calculation

    102        first_name = 'person'
    103        last_name = 'public'
    104        year_of_birth = 2000
    105        age = (
    106            datetime.datetime.now().year
    107          - year_of_birth
    108        )
    109
    110        a_person = src.person.factory(
    111            # first_name='person',
    112            # last_name='public',
    113            # year_of_birth=2000,
    114            first_name=first_name,
    115            last_name=last_name,
    116            year_of_birth=year_of_birth,
    117            sex='F',
    118        )
    119
    120        reality = src.person.say_hello(a_person)
    121        my_expectation = (
    122            # 'Hi, my name is person public and I am'
    123            # f' {datetime.datetime.now().year-2000}'
    124            f'Hi, my name is {first_name} {last_name}'
    125            f' and I am {age}'
    126        )
    127        self.assertEqual(reality, my_expectation)
    128
    129
    130# Exceptions seen
    

    the test is still green.

  • I add variables to use them to remove repetition of 'john', 'smith', 1580 and the age calculation from the assertion before the one for a_person

     76        jane = src.person.factory(
     77            first_name='jane',
     78            sex='F',
     79            year_of_birth=1991,
     80        )
     81
     82        reality = src.person.say_hello(jane)
     83        my_expectation = (
     84            'Hi, my name is jane doe and I am'
     85            f' {datetime.datetime.now().year-1991}'
     86        )
     87        self.assertEqual(reality, my_expectation)
     88
     89        first_name = 'john'
     90        last_name = 'smith'
     91        year_of_birth = 1580
     92        age = (
     93            datetime.datetime.now().year
     94          - year_of_birth
     95        )
     96
     97        john = src.person.factory(
     98            first_name='john',
     99            last_name='smith',
    100            year_of_birth=1580,
    101        )
    
  • I use the variables to remove repetition of 'john', 'smith', 1580 and the age calculation

     89        first_name = 'john'
     90        last_name = 'smith'
     91        year_of_birth = 1580
     92        age = (
     93            datetime.datetime.now().year
     94          - year_of_birth
     95        )
     96
     97        john = src.person.factory(
     98            # first_name='john',
     99            # last_name='smith',
    100            # year_of_birth=1580,
    101            first_name=first_name,
    102            last_name=last_name,
    103            year_of_birth=year_of_birth,
    104        )
    105
    106        reality = src.person.say_hello(john)
    107        my_expectation = (
    108            # 'Hi, my name is john smith and I am'
    109            # f' {datetime.datetime.now().year-1580}'
    110            f'Hi, my name is {first_name} {last_name}'
    111            f' and I am {age}'
    112        )
    113        self.assertEqual(reality, my_expectation)
    114
    115        first_name = 'person'
    116        last_name = 'public'
    117        year_of_birth = 2000
    118        age = (
    119            datetime.datetime.now().year
    120          - year_of_birth
    121        )
    

    still green.

  • I add the same variable names to use them to remove repetition of 'jane', 1991 and the age calculation from the second assertion

    62    def test_factory_person_says_hello(self):
    63        joe = src.person.factory(
    64            first_name='joe',
    65            last_name='blow',
    66            year_of_birth=1996,
    67        )
    68
    69        reality = src.person.say_hello(joe)
    70        my_expectation = (
    71            'Hi, my name is joe blow and I am'
    72            f' {datetime.datetime.now().year-1996}'
    73        )
    74        self.assertEqual(reality, my_expectation)
    75
    76        first_name = 'jane'
    77        last_name = 'doe'
    78        year_of_birth = 1991
    79        age = (
    80            datetime.datetime.now().year
    81          - year_of_birth
    82        )
    83
    84        jane = src.person.factory(
    85            first_name='jane',
    86            sex='F',
    87            year_of_birth=1991,
    88        )
    
  • I use the variables to remove repetition of 'jane', 1991 and the age calculation

     76        first_name = 'jane'
     77        last_name = 'doe'
     78        year_of_birth = 1991
     79        age = (
     80            datetime.datetime.now().year
     81          - year_of_birth
     82        )
     83
     84        jane = src.person.factory(
     85            # first_name='jane',
     86            sex='F',
     87            # year_of_birth=1991,
     88            first_name=first_name,
     89            year_of_birth=year_of_birth,
     90        )
     91
     92        reality = src.person.say_hello(jane)
     93        my_expectation = (
     94            # 'Hi, my name is jane doe and I am'
     95            # f' {datetime.datetime.now().year-1991}'
     96            f'Hi, my name is {first_name} {last_name}'
     97            f' and I am {age}'
     98        )
     99        self.assertEqual(reality, my_expectation)
    100
    101        first_name = 'john'
    102        last_name = 'smith'
    103        year_of_birth = 1580
    104        age = (
    105            datetime.datetime.now().year
    106          - year_of_birth
    107        )
    

    green.

  • I add the variable names to use them to remove repetition of 'joe', 'blow', 1996 and the age calculation from the first assertion

    64    def test_factory_person_says_hello(self):
    65        first_name = 'joe'
    66        last_name = 'blow'
    67        year_of_birth = 1996
    68        age = (
    69            datetime.datetime.now().year
    70          - year_of_birth
    71        )
    72
    73        joe = src.person.factory(
    74            first_name='joe',
    75            last_name='blow',
    76            year_of_birth=1996,
    77        )
    
  • I use the variables to remove repetition of 'joe', 'blow', 1996 and the age calculation

    62    def test_factory_person_says_hello(self):
    63        first_name = 'joe'
    64        last_name = 'blow'
    65        year_of_birth = 1996
    66        age = (
    67            datetime.datetime.now().year
    68          - year_of_birth
    69        )
    70
    71        joe = src.person.factory(
    72            # first_name='joe',
    73            # last_name='blow',
    74            # year_of_birth=1996,
    75            first_name=first_name,
    76            last_name=last_name,
    77            year_of_birth=year_of_birth,
    78        )
    79
    80        reality = src.person.say_hello(joe)
    81        my_expectation = (
    82            # 'Hi, my name is joe blow and I am'
    83            # f' {datetime.datetime.now().year-1996}'
    84            f'Hi, my name is {first_name} {last_name}'
    85            f' and I am {age}'
    86        )
    87        self.assertEqual(reality, my_expectation)
    88
    89        first_name = 'jane'
    90        last_name = 'doe'
    91        year_of_birth = 1991
    92        age = (
    93            datetime.datetime.now().year
    94          - year_of_birth
    95        )
    

    the test is still green.

  • I remove the commented lines

     62    def test_factory_person_says_hello(self):
     63        first_name = 'joe'
     64        last_name = 'blow'
     65        year_of_birth = 1996
     66        age = (
     67            datetime.datetime.now().year
     68          - year_of_birth
     69        )
     70
     71        joe = src.person.factory(
     72            first_name=first_name,
     73            last_name=last_name,
     74            year_of_birth=year_of_birth,
     75        )
     76
     77        reality = src.person.say_hello(joe)
     78        my_expectation = (
     79            f'Hi, my name is {first_name} {last_name}'
     80            f' and I am {age}'
     81        )
     82        self.assertEqual(reality, my_expectation)
     83
     84        first_name = 'jane'
     85        last_name = 'doe'
     86        year_of_birth = 1991
     87        age = (
     88            datetime.datetime.now().year
     89          - year_of_birth
     90        )
     91
     92        jane = src.person.factory(
     93            sex='F',
     94            first_name=first_name,
     95            year_of_birth=year_of_birth,
     96        )
     97
     98        reality = src.person.say_hello(jane)
     99        my_expectation = (
    100            f'Hi, my name is {first_name} {last_name}'
    101            f' and I am {age}'
    102        )
    103        self.assertEqual(reality, my_expectation)
    104
    105        first_name = 'john'
    106        last_name = 'smith'
    107        year_of_birth = 1580
    108        age = (
    109            datetime.datetime.now().year
    110          - year_of_birth
    111        )
    112
    113        john = src.person.factory(
    114            first_name=first_name,
    115            last_name=last_name,
    116            year_of_birth=year_of_birth,
    117        )
    118
    119        reality = src.person.say_hello(john)
    120        my_expectation = (
    121            f'Hi, my name is {first_name} {last_name}'
    122            f' and I am {age}'
    123        )
    124        self.assertEqual(reality, my_expectation)
    125
    126        first_name = 'person'
    127        last_name = 'public'
    128        year_of_birth = 2000
    129        age = (
    130            datetime.datetime.now().year
    131          - year_of_birth
    132        )
    133
    134        a_person = src.person.factory(
    135            first_name=first_name,
    136            last_name=last_name,
    137            year_of_birth=year_of_birth,
    138            sex='F',
    139        )
    140
    141        reality = src.person.say_hello(a_person)
    142        my_expectation = (
    143            f'Hi, my name is {first_name} {last_name}'
    144            f' and I am {age}'
    145        )
    146        self.assertEqual(reality, my_expectation)
    147
    148
    149# Exceptions seen
    
  • I add a git commit message in the other terminal

    git commit -am 'add test_factory_person_says_hello'
    

    the terminal shows a summary of the changes then goes back to the command line.


extract calculate_age function

Each assertion in every test has a calculation for the age

  • I go back to the terminal that is running the tests

  • I add a function to use to remove repetition of the age calculation

    11def get_random_name():
    12    return pick_one(
    13        'jane', 'joe', 'john', 'person',
    14        'doe', 'smith', 'blow', 'public',
    15    )
    16
    17
    18def calculate_age(year_of_birth):
    19    return (
    20        datetime.datetime.now().year
    21      - year_of_birth
    22    )
    23
    24
    25class TestPerson(unittest.TestCase):
    26
    27    def test_factory_w_keyword_arguments(self):
    
  • I use the function to remove repetition of the age calculation from my_expectation in test_factory_w_keyword_arguments

    39        reality = src.person.factory(
    40            **a_person,
    41            year_of_birth=year_of_birth,
    42        )
    43        my_expectation = dict(
    44            **a_person,
    45            # age=this_year-year_of_birth,
    46            age=calculate_age(year_of_birth),
    47        )
    48        self.assertEqual(reality, my_expectation)
    
  • I remove the commented line

    27    def test_factory_w_keyword_arguments(self):
    28        a_person = dict(
    29            first_name=get_random_name(),
    30            last_name=get_random_name(),
    31            sex=pick_one('F', 'M'),
    32        )
    33
    34        this_year = datetime.datetime.now().year
    35        year_of_birth = random.randint(
    36            this_year-120, this_year
    37        )
    38
    39        reality = src.person.factory(
    40            **a_person,
    41            year_of_birth=year_of_birth,
    42        )
    43        my_expectation = dict(
    44            **a_person,
    45            age=calculate_age(year_of_birth),
    46        )
    47        self.assertEqual(reality, my_expectation)
    48
    49    def test_factory_w_optional_arguments(self):
    

    the test is still green.

  • I use the function to remove repetition of the age calculation from my_expectation in test_factory_w_optional_arguments

    57        reality = src.person.factory(
    58            first_name=first_name,
    59            year_of_birth=year_of_birth,
    60        )
    61        my_expectation = dict(
    62            first_name=first_name,
    63            last_name='doe',
    64            sex='M',
    65            # age=this_year-year_of_birth,
    66            age=calculate_age(year_of_birth),
    67        )
    68        self.assertEqual(reality, my_expectation)
    69
    70    def test_factory_person_says_hello(self):
    
  • I remove the commented line

    49    def test_factory_w_optional_arguments(self):
    50        first_name = get_random_name()
    51
    52        this_year = datetime.datetime.now().year
    53        year_of_birth = random.randint(
    54            this_year-120, this_year
    55        )
    56
    57        reality = src.person.factory(
    58            first_name=first_name,
    59            year_of_birth=year_of_birth,
    60        )
    61        my_expectation = dict(
    62            first_name=first_name,
    63            last_name='doe',
    64            sex='M',
    65            age=calculate_age(year_of_birth),
    66        )
    67        self.assertEqual(reality, my_expectation)
    68
    69    def test_factory_person_says_hello(self):
    

    the test is still green.

  • I use the function to remove repetition of the age calculation from my_expectation for joe in test_factory_person_says_hello

    69    def test_factory_person_says_hello(self):
    70        first_name = 'joe'
    71        last_name = 'blow'
    72        year_of_birth = 1996
    73        # age = (
    74        #     datetime.datetime.now().year
    75        #   - year_of_birth
    76        # )
    77
    78        joe = src.person.factory(
    79            first_name=first_name,
    80            last_name=last_name,
    81            year_of_birth=year_of_birth,
    82        )
    83
    84        reality = src.person.say_hello(joe)
    85        my_expectation = (
    86            f'Hi, my name is {first_name} {last_name}'
    87            # f' and I am {age}'
    88            f' and I am {calculate_age(year_of_birth)}'
    89        )
    90        self.assertEqual(reality, my_expectation)
    

    the test is still green.

  • I use the function to remove repetition of the age calculation from my_expectation for jane in test_factory_person_says_hello

     84        reality = src.person.say_hello(joe)
     85        my_expectation = (
     86            f'Hi, my name is {first_name} {last_name}'
     87            # f' and I am {age}'
     88            f' and I am {calculate_age(year_of_birth)}'
     89        )
     90        self.assertEqual(reality, my_expectation)
     91
     92        first_name = 'jane'
     93        last_name = 'doe'
     94        year_of_birth = 1991
     95        # age = (
     96        #     datetime.datetime.now().year
     97        #   - year_of_birth
     98        # )
     99
    100        jane = src.person.factory(
    101            sex='F',
    102            first_name=first_name,
    103            year_of_birth=year_of_birth,
    104        )
    105
    106        reality = src.person.say_hello(jane)
    107        my_expectation = (
    108            f'Hi, my name is {first_name} {last_name}'
    109            # f' and I am {age}'
    110            f' and I am {calculate_age(year_of_birth)}'
    111        )
    112        self.assertEqual(reality, my_expectation)
    

    still green.

  • I use the function to remove repetition of the age calculation from my_expectation for john in test_factory_person_says_hello

    106        reality = src.person.say_hello(jane)
    107        my_expectation = (
    108            f'Hi, my name is {first_name} {last_name}'
    109            # f' and I am {age}'
    110            f' and I am {calculate_age(year_of_birth)}'
    111        )
    112        self.assertEqual(reality, my_expectation)
    113
    114        first_name = 'john'
    115        last_name = 'smith'
    116        year_of_birth = 1580
    117        # age = (
    118        #     datetime.datetime.now().year
    119        #   - year_of_birth
    120        # )
    121
    122        john = src.person.factory(
    123            first_name=first_name,
    124            last_name=last_name,
    125            year_of_birth=year_of_birth,
    126        )
    127
    128        reality = src.person.say_hello(john)
    129        my_expectation = (
    130            f'Hi, my name is {first_name} {last_name}'
    131            # f' and I am {age}'
    132            f' and I am {calculate_age(year_of_birth)}'
    133        )
    134        self.assertEqual(reality, my_expectation)
    

    green.

  • I use the function to remove repetition of the age calculation from my_expectation for a_person in test_factory_person_says_hello

    136        first_name = 'person'
    137        last_name = 'public'
    138        year_of_birth = 2000
    139        # age = (
    140        #     datetime.datetime.now().year
    141        #   - year_of_birth
    142        # )
    143
    144        a_person = src.person.factory(
    145            first_name=first_name,
    146            last_name=last_name,
    147            year_of_birth=year_of_birth,
    148            sex='F',
    149        )
    150
    151        reality = src.person.say_hello(a_person)
    152        my_expectation = (
    153            f'Hi, my name is {first_name} {last_name}'
    154            # f' and I am {age}'
    155            f' and I am {calculate_age(year_of_birth)}'
    156        )
    157        self.assertEqual(reality, my_expectation)
    158
    159
    160# Exceptions seen
    

    the test is still green.

  • I remove the commented lines

     69    def test_factory_person_says_hello(self):
     70        first_name = 'joe'
     71        last_name = 'blow'
     72        year_of_birth = 1996
     73
     74        joe = src.person.factory(
     75            first_name=first_name,
     76            last_name=last_name,
     77            year_of_birth=year_of_birth,
     78        )
     79
     80        reality = src.person.say_hello(joe)
     81        my_expectation = (
     82            f'Hi, my name is {first_name} {last_name}'
     83            f' and I am {calculate_age(year_of_birth)}'
     84        )
     85        self.assertEqual(reality, my_expectation)
     86
     87        first_name = 'jane'
     88        last_name = 'doe'
     89        year_of_birth = 1991
     90
     91        jane = src.person.factory(
     92            sex='F',
     93            first_name=first_name,
     94            year_of_birth=year_of_birth,
     95        )
     96
     97        reality = src.person.say_hello(jane)
     98        my_expectation = (
     99            f'Hi, my name is {first_name} {last_name}'
    100            f' and I am {calculate_age(year_of_birth)}'
    101        )
    102        self.assertEqual(reality, my_expectation)
    103
    104        first_name = 'john'
    105        last_name = 'smith'
    106        year_of_birth = 1580
    107
    108        john = src.person.factory(
    109            first_name=first_name,
    110            last_name=last_name,
    111            year_of_birth=year_of_birth,
    112        )
    113
    114        reality = src.person.say_hello(john)
    115        my_expectation = (
    116            f'Hi, my name is {first_name} {last_name}'
    117            f' and I am {calculate_age(year_of_birth)}'
    118        )
    119        self.assertEqual(reality, my_expectation)
    120
    121        first_name = 'person'
    122        last_name = 'public'
    123        year_of_birth = 2000
    124
    125        a_person = src.person.factory(
    126            first_name=first_name,
    127            last_name=last_name,
    128            year_of_birth=year_of_birth,
    129            sex='F',
    130        )
    131
    132        reality = src.person.say_hello(a_person)
    133        my_expectation = (
    134            f'Hi, my name is {first_name} {last_name}'
    135            f' and I am {calculate_age(year_of_birth)}'
    136        )
    137        self.assertEqual(reality, my_expectation)
    138
    139
    140# Exceptions seen
    

    the test is still green.

  • I add a git commit message in the other terminal

    git commit -am 'extract calculate_age function'
    

    the terminal shows a summary of the changes then goes back to the command line.


test say_hello with random values

I want to use random values in test_factory_person_says_hello

  • I go back to the terminal that is running the tests

  • I add random values to use for the first_name, last_name, sex and year_of_birth variables

    69    def test_factory_person_says_hello(self):
    70        first_name = get_random_name()
    71        last_name = get_random_name()
    72        sex = pick_one('F', 'M')
    73        this_year = datetime.datetime.now().year
    74        year_of_birth = random.randint(
    75            this_year-120, this_year
    76        )
    77
    78        first_name = 'joe'
    79        last_name = 'blow'
    80        year_of_birth = 1996
    
  • I comment out the first_name, last_name and year_of_birth variables for joe in the first assertion, so it uses the ones with random values

    69    def test_factory_person_says_hello(self):
    70        first_name = get_random_name()
    71        last_name = get_random_name()
    72        sex = pick_one('F', 'M')
    73        this_year = datetime.datetime.now().year
    74        year_of_birth = random.randint(
    75            this_year-120, this_year
    76        )
    77
    78        # first_name = 'joe'
    79        # last_name = 'blow'
    80        # year_of_birth = 1996
    81
    82        joe = src.person.factory(
    83            first_name=first_name,
    84            last_name=last_name,
    85            year_of_birth=year_of_birth,
    86        )
    

    the test is still green.

  • I add a random value for the sex parameter with the sex variable in the call to src.person.factory for joe

    82        joe = src.person.factory(
    83            first_name=first_name,
    84            last_name=last_name,
    85            year_of_birth=year_of_birth,
    86            sex=sex
    87        )
    

    still green.

  • I comment out the first_name, last_name and year_of_birth variables for jane in the second assertion, so it uses the ones with random values

     89        reality = src.person.say_hello(joe)
     90        my_expectation = (
     91            f'Hi, my name is {first_name} {last_name}'
     92            f' and I am {calculate_age(year_of_birth)}'
     93        )
     94        self.assertEqual(reality, my_expectation)
     95
     96        # first_name = 'jane'
     97        # last_name = 'doe'
     98        # year_of_birth = 1991
     99
    100        jane = src.person.factory(
    101            sex='F',
    102            first_name=first_name,
    103            year_of_birth=year_of_birth,
    104        )
    

    I use ctrl+s (Windows/Linux) or command+s (MacOS) to run the test a few times and it passes if the last_name is randomly 'doe'.

    If the last_name is not 'doe', the terminal is my friend, and shows AssertionError

    AssertionError:
        'Hi, my name is X doe and I am Z'
     != 'Hi, my name is X Y and I am Z'
    

    because I did not need to give a value for the last_name parameter in the call to src.person.factory since the default value for the last_name parameter of the function is 'doe'. This means that

    src.person.factory(
        first_name=first_name,
        sex='F',
        year_of_birth=year_of_birth,
    )
    

    is the same as

    src.person.factory(
        first_name=first_name,
        sex='F',
        year_of_birth=year_of_birth,
        last_name='doe'
    )
    

    A function uses the default value for a parameter when it is called without the parameter.

  • I add last_name to the call to src.person.factory for jane to use the variable

    100        jane = src.person.factory(
    101            sex='F',
    102            first_name=first_name,
    103            year_of_birth=year_of_birth,
    104            last_name=last_name,
    105        )
    

    I use ctrl+s (Windows/Linux) or command+s (MacOS) to run the test a few times and it passes with no more random failures.

  • I change the value for the sex parameter to the sex variable so it uses a random value in the call to src.person.factory for jane

    100        jane = src.person.factory(
    101            # sex='F',
    102            first_name=first_name,
    103            year_of_birth=year_of_birth,
    104            last_name=last_name,
    105            sex=sex,
    106        )
    

    the test is still green.

  • I comment out the first_name, last_name and year_of_birth variables for john in the third assertion, so it uses the ones with random values

    108        reality = src.person.say_hello(jane)
    109        my_expectation = (
    110            f'Hi, my name is {first_name} {last_name}'
    111            f' and I am {calculate_age(year_of_birth)}'
    112        )
    113        self.assertEqual(reality, my_expectation)
    114
    115        # first_name = 'john'
    116        # last_name = 'smith'
    117        # year_of_birth = 1580
    118
    119        john = src.person.factory(
    120            first_name=first_name,
    121            last_name=last_name,
    122            year_of_birth=year_of_birth,
    123        )
    

    still green.

  • I add a random value for the sex parameter with the sex variable in the call to src.person.factory for john

    119        john = src.person.factory(
    120            first_name=first_name,
    121            last_name=last_name,
    122            year_of_birth=year_of_birth,
    123            sex=sex,
    124        )
    

    green.

  • I comment out the first_name, last_name and year_of_birth variables for a_person in the last assertion, so it uses the ones with random values

    126        reality = src.person.say_hello(john)
    127        my_expectation = (
    128            f'Hi, my name is {first_name} {last_name}'
    129            f' and I am {calculate_age(year_of_birth)}'
    130        )
    131        self.assertEqual(reality, my_expectation)
    132
    133        # first_name = 'person'
    134        # last_name = 'public'
    135        # year_of_birth = 2000
    136
    137        a_person = src.person.factory(
    138            first_name=first_name,
    139            last_name=last_name,
    140            year_of_birth=year_of_birth,
    141            sex='F',
    142        )
    

    the test is still green.

  • I add a random value for the sex parameter with the sex variable in the call to src.person.factory for a_person

    137        a_person = src.person.factory(
    138            first_name=first_name,
    139            last_name=last_name,
    140            year_of_birth=year_of_birth,
    141            # sex='F',
    142            sex=sex,
    143        )
    

    still green.

  • I add a random person that covers all the possible cases because the four people I made in test_factory_person_says_hello now all use random values, and the assertions are the same for each case because they use the first_name, last_name and age variables that are sent in the call to src.person.factory

    145        reality = src.person.say_hello(a_person)
    146        my_expectation = (
    147            f'Hi, my name is {first_name} {last_name}'
    148            f' and I am {calculate_age(year_of_birth)}'
    149        )
    150        self.assertEqual(reality, my_expectation)
    151
    152        a_random_person = src.person.factory(
    153            first_name=first_name,
    154            last_name=last_name,
    155            sex=sex,
    156            year_of_birth=year_of_birth,
    157        )
    158
    159        reality = src.person.say_hello(a_random_person)
    160        my_expectation = ''
    161        self.assertEqual(reality, my_expectation)
    162
    163
    164# Exceptions seen
    

    the terminal is my friend and shows AssertionError

    AssertionError: 'Hi, my name is Z Y and I am X' != ''
    
  • I change my_expectation to match reality

    152        a_random_person = src.person.factory(
    153            first_name=first_name,
    154            last_name=last_name,
    155            sex=sex,
    156            year_of_birth=year_of_birth,
    157        )
    158
    159        reality = src.person.say_hello(a_random_person)
    160        my_expectation = (
    161            f'Hi, my name is {first_name} {last_name}'
    162            f' and I am {calculate_age(year_of_birth)}'
    163        )
    164        self.assertEqual(reality, my_expectation)
    165
    166
    167# Exceptions seen
    

    the test is green

  • I remove the commented lines and the other people from test_factory_person_says_hello because they are now all the same

    69    def test_factory_person_says_hello(self):
    70        first_name = get_random_name()
    71        last_name = get_random_name()
    72        sex = pick_one('F', 'M')
    73        this_year = datetime.datetime.now().year
    74        year_of_birth = random.randint(
    75            this_year-120, this_year
    76        )
    77
    78        a_random_person = src.person.factory(
    79            first_name=first_name,
    80            last_name=last_name,
    81            sex=sex,
    82            year_of_birth=year_of_birth,
    83        )
    84
    85        reality = src.person.say_hello(a_random_person)
    86        my_expectation = (
    87            f'Hi, my name is {first_name} {last_name}'
    88            f' and I am {calculate_age(year_of_birth)}'
    89        )
    90        self.assertEqual(reality, my_expectation)
    91
    92
    93# Exceptions seen
    
  • I add a git commit message in the other terminal

    git commit -am 'test say_hello with random values'
    

    the terminal shows a summary of the changes then goes back to the command line.


extract get_random_year_of_birth function

I make the this_year and year_of_birth variables the same way in all three tests.

  • I go back to the terminal that is running the tests

  • I add a function to use to replace the repetition of making the values for the this_year and year_of_birth variables in test_person.py

    18def calculate_age(year_of_birth):
    19    return (
    20        datetime.datetime.now().year
    21      - year_of_birth
    22    )
    23
    24
    25def get_random_year_of_birth():
    26    this_year = datetime.datetime.now().year
    27    return random.randint(
    28        this_year-120, this_year
    29    )
    30
    31
    32class TestPerson(unittest.TestCase):
    33
    34    def test_factory_w_keyword_arguments(self):
    
  • I use the get_random_year_of_birth function to replace the repetition of making the values for the this_year and year_of_birth in test_factory_w_keyword_arguments

    34    def test_factory_w_keyword_arguments(self):
    35        a_person = dict(
    36            first_name=get_random_name(),
    37            last_name=get_random_name(),
    38            sex=pick_one('F', 'M'),
    39        )
    40
    41        # this_year = datetime.datetime.now().year
    42        # year_of_birth = random.randint(
    43        #     this_year-120, this_year
    44        # )
    45        year_of_birth = get_random_year_of_birth()
    

    the test is still green.

  • I remove the commented lines

    34    def test_factory_w_keyword_arguments(self):
    35        a_person = dict(
    36            first_name=get_random_name(),
    37            last_name=get_random_name(),
    38            sex=pick_one('F', 'M'),
    39        )
    40        year_of_birth = get_random_year_of_birth()
    41
    42        reality = src.person.factory(
    43            **a_person,
    44            year_of_birth=year_of_birth,
    45        )
    46        my_expectation = dict(
    47            **a_person,
    48            age=calculate_age(year_of_birth),
    49        )
    50        self.assertEqual(reality, my_expectation)
    51
    52    def test_factory_w_optional_arguments(self):
    
  • I use the get_random_year_of_birth function to replace the repetition of making the values for the this_year and year_of_birth in test_factory_w_optional_arguments

    52    def test_factory_w_optional_arguments(self):
    53        first_name = get_random_name()
    54
    55        # this_year = datetime.datetime.now().year
    56        # year_of_birth = random.randint(
    57        #     this_year-120, this_year
    58        # )
    59        year_of_birth = get_random_year_of_birth()
    

    the test is still green.

  • I remove the commented lines

    52    def test_factory_w_optional_arguments(self):
    53        first_name = get_random_name()
    54        year_of_birth = get_random_year_of_birth()
    55
    56        reality = src.person.factory(
    57            first_name=first_name,
    58            year_of_birth=year_of_birth,
    59        )
    60        my_expectation = dict(
    61            first_name=first_name,
    62            last_name='doe',
    63            sex='M',
    64            age=calculate_age(year_of_birth),
    65        )
    66        self.assertEqual(reality, my_expectation)
    67
    68    def test_factory_person_says_hello(self):
    
  • I use the get_random_year_of_birth function to replace the repetition of making the values for the this_year and year_of_birth in test_factory_person_says_hello

    68    def test_factory_person_says_hello(self):
    69        first_name = get_random_name()
    70        last_name = get_random_name()
    71        sex = pick_one('F', 'M')
    72        # this_year = datetime.datetime.now().year
    73        # year_of_birth = random.randint(
    74        #     this_year-120, this_year
    75        # )
    76        year_of_birth = get_random_year_of_birth()
    

    the test is still green.

  • I remove the commented lines

    67    def test_factory_person_says_hello(self):
    68        first_name = get_random_name()
    69        last_name = get_random_name()
    70        sex = pick_one('F', 'M')
    71        year_of_birth = get_random_year_of_birth()
    72
    73        a_random_person = src.person.factory(
    74            first_name=first_name,
    75            last_name=last_name,
    76            sex=sex,
    77            year_of_birth=year_of_birth,
    78        )
    79
    80        reality = src.person.say_hello(a_random_person)
    81        my_expectation = (
    82            f'Hi, my name is {first_name} {last_name}'
    83            f' and I am {calculate_age(year_of_birth)}'
    84        )
    85        self.assertEqual(reality, my_expectation)
    86
    87
    88# Exceptions seen
    89# AssertionError
    90# NameError
    91# AttributeError
    92# TypeError
    93# SyntaxError
    
  • I add a git commit message in the other terminal

    git commit -am \
    'extract get_random_year_of_birth function'
    

    the terminal shows a summary of the changes then goes back to the command line.


test_update_factory_person_year_of_birth

I made a person named john in test_factory_person_says_hello with a year of birth of 1580, which means he is too old to be alive. That age is also outside the range I used for the random age, the oldest person the test makes is 120.

Maybe I made a mistake when typing his age and typed 5 instead of 9. How would I change the year of birth of a person made with the factory function if I cannot change the original year of birth?

  • I could try updating the year_of_birth

  • I could try making a function that takes a person and a new year of birth as inputs, and returns the person with the corrected age

  • I could make a new factory function that returns a dictionary with year_of_birth as a key which allows me to change it, then make another function that calculates the age from the returned dictionary - this sounds like a lot of work, I would also have to rewrite the tests. No, thank you.


RED: make it fail


I add a new test for

100          for person in (joe, jane, john):
101              with self.subTest(name=person.first_name):
102                  self.assertEqual(
103                      person.hello(),
104                      (
105                          f'Hi, my name is {person.first_name} '
106                          f'{person.last_name} '
107                          f'and I am {person.get_age()}'
108                      )
109                  )
110
111      def test_update_factory_person_year_of_birth(self):
112          person = src.person.factory(
113              first_name='john',
114              last_name='smith',
115              year_of_birth=1580,
116          )
117          self.assertEqual(person.get('age'), 0)
118
119
120  # Exceptions seen

the terminal is my friend, and shows AssertionError

AssertionError: 446 != 0

GREEN: make it passs


I change the expectation

117        self.assertEqual(person.get('age'), 446)

the test passes.


REFACTOR: make it better


  • I try to use the year_of_birth key

    117        self.assertEqual(person.get('age'), 446)
    118
    119        person['year_of_birth']
    120
    121
    122# Exceptions seen
    

    the terminal is my friend, and shows KeyError

    KeyError: 'year_of_birth'
    

    there is no year_of_birth key in the dictionary returned by the factory function

  • I add KeyError to the list of Exceptions seen

    122# Exceptions seen
    123# AssertionError
    124# NameError
    125# AttributeError
    126# TypeError
    127# SyntaxError
    128# KeyError
    
  • I add assertRaises

    117        self.assertEqual(person.get('age'), 446)
    118
    119        with self.assertRaises(KeyError):
    120            person['year_of_birth']
    121
    122
    123# Exceptions seen
    

    the test passes.

  • I add a new value for year_of_birth with the setdefault method

    119        with self.assertRaises(KeyError):
    120            person['year_of_birth']
    121        self.assertEqual(
    122            person.setdefault('year_of_birth', 1980),
    123            None
    124        )
    125
    126
    127# Exceptions seen
    

    the terminal is my friend, and shows AssertionError

    AssertionError: 1980 != None
    
  • I change the expectation

    121        self.assertEqual(
    122            person.setdefault('year_of_birth', 1980),
    123            1980
    124        )
    

    the test passes because the dictionary now has a key named year_of_birth with the new value

  • I add an assertion for the age of john smith again

    121        self.assertEqual(
    122            person.setdefault('year_of_birth', 1980),
    123            1980
    124        )
    125        self.assertEqual(person.get('age'), 46)
    126
    127# Exceptions seen
    

    the terminal is my friend, and shows AssertionError

    AssertionError: 446 != 46
    

    the factory function uses the value for year_of_birth to calculate the age when it makes the dictionary.

    When I change the value or add the key, it does not do anything to the age. There has to be a better way

  • I change the expectation

    125self.assertEqual(person.get('age'), 446)
    

    the test passes.


  • I can make a function that takes a person and a new year of birth as inputs, and returns the person with the correct age. I add an assertion

    125        self.assertEqual(person.get('age'), 446)
    126
    127        self.assertEqual(
    128            src.person.update_year_of_birth(person, 1980),
    129            dict(
    130                first_name=person.get('first_name'),
    131                last_name=person.get('last_name'),
    132                sex=person.get('sex'),
    133                age=this_year()-1980,
    134            )
    135        )
    136
    137
    138# Exceptions seen
    

    the terminal is my friend, and shows AttributeError

    AttributeError: module 'src.person' has no attribute 'update_year_of_birth'
    
  • I add the function to person.py

    16def say_hello(person):
    17    return (
    18        f'Hi, my name is {person.get("first_name")} '
    19        f'{person.get("last_name")} '
    20        f'and I am {person.get("age")}'
    21    )
    22
    23
    24def update_year_of_birth():
    25    return None
    26
    27
    28class Person:
    

    the terminal is my friend, and shows TypeError

    TypeError: update_year_of_birth() takes 0 positional arguments but 2 were given
    
  • I add the two names in parentheses

    27def update_year_of_birth(person, new_year_of_birth):
    28    return None
    

    the terminal is my friend, and shows AssertionError

    AssertionError: None != {'first_name': 'john', 'last_name': 'smith', 'sex': 'M', 'age': 46}
    
  • I change the return statement

    27def update_year_of_birth(person, new_year_of_birth):
    28    return factory(
    29        first_name=person.get('first_name'),
    30        last_name=person.get('last_name'),
    31        sex=person.get('sex'),
    32        year_of_birth=new_year_of_birth,
    33    )
    34
    35
    36class Person:
    

    the test passes.

I cannot update the year_of_birth key because the function returns a dictionary that does not have a year_of_birth key.


  • time to remove some repetition. I add a variable for the original year of birth in test_update_factory_person_year_of_birth in test_person.py

    111    def test_update_factory_person_year_of_birth(self):
    112        original_year_of_birth = 1580
    113
    114        person = src.person.factory(
    

    the test is still green.

  • I use the variable in the call to src.person.factory

    112        original_year_of_birth = 1580
    113
    114        person = src.person.factory(
    115            first_name='john',
    116            last_name='smith',
    117            # year_of_birth=1580,
    118            year_of_birth=original_year_of_birth,
    119        )
    

    still green.

  • I use the variable in the first assertion for the age

    114        person = src.person.factory(
    115            first_name='john',
    116            last_name='smith',
    117            # year_of_birth=1580,
    118            year_of_birth=original_year_of_birth,
    119        )
    120        # self.assertEqual(person.get('age'), 446)
    121        self.assertEqual(
    122            person.get('age'),
    123            this_year()-original_year_of_birth
    124        )
    125
    126        with self.assertRaises(KeyError):
    

    green.

  • I use the variable in the second assertion for the age

    126        with self.assertRaises(KeyError):
    127            person['year_of_birth']
    128        self.assertEqual(
    129            person.setdefault('year_of_birth', 1980),
    130            1980
    131        )
    132        # self.assertEqual(person.get('age'), 446)
    133        self.assertEqual(
    134            person.get('age'),
    135            this_year()-original_year_of_birth
    136        )
    137
    138        self.assertEqual(
    

    still green.

  • I remove the commented lines

    111    def test_update_factory_person_year_of_birth(self):
    112        original_year_of_birth = 1580
    113
    114        person = src.person.factory(
    115            first_name='john',
    116            last_name='smith',
    117            year_of_birth=original_year_of_birth,
    118        )
    119        self.assertEqual(
    120            person.get('age'),
    121            this_year()-original_year_of_birth
    122        )
    123
    124        with self.assertRaises(KeyError):
    125            person['year_of_birth']
    126        self.assertEqual(
    127            person.setdefault('year_of_birth', 1980),
    128            1980
    129        )
    130        self.assertEqual(
    131            person.get('age'),
    132            this_year()-original_year_of_birth
    133        )
    134
    135        self.assertEqual(
    136            src.person.update_year_of_birth(person, 1980),
    137            dict(
    138                first_name=person.get('first_name'),
    139                last_name=person.get('last_name'),
    140                sex=person.get('sex'),
    141                age=this_year()-1980,
    142            )
    143        )
    144
    145
    146# Exceptions seen
    

    the test is still green.


  • I add a variable for the new year of birth

    111    def test_update_factory_person_year_of_birth(self):
    112        original_year_of_birth = 1580
    113        new_year_of_birth = 1980
    114
    115        person = src.person.factory(
    

    still green.

  • I use the variable in the assertion for the call to the setdefault method

    125          with self.assertRaises(KeyError):
    126              person['year_of_birth']
    127          self.assertEqual(
    128              # person.setdefault('year_of_birth', 1980),
    129              person.setdefault('year_of_birth', new_year_of_birth),
    130              # 1980
    131              new_year_of_birth
    132          )
    133          self.assertEqual(
    

    the test is still green.

  • I use the variable in the last assertion

    138        self.assertEqual(
    139            # src.person.update_year_of_birth(person, 1980),
    140            src.person.update_year_of_birth(
    141                person,
    142                new_year_of_birth
    143            ),
    144            dict(
    145                first_name=person.get('first_name'),
    146                last_name=person.get('last_name'),
    147                sex=person.get('sex'),
    148                # age=this_year()-1980,
    149                age=this_year()-new_year_of_birth,
    150            )
    151        )
    152
    153
    154# Exceptions seen
    

    still green.

  • I remove the commented lines

    111    def test_update_factory_person_year_of_birth(self):
    112        original_year_of_birth = 1580
    113        new_year_of_birth = 1980
    114
    115        person = src.person.factory(
    116            first_name='john',
    117            last_name='smith',
    118            year_of_birth=original_year_of_birth,
    119        )
    120        self.assertEqual(
    121            person.get('age'),
    122            this_year()-original_year_of_birth
    123        )
    124
    125        with self.assertRaises(KeyError):
    126            person['year_of_birth']
    127        self.assertEqual(
    128            person.setdefault('year_of_birth', new_year_of_birth),
    129            new_year_of_birth
    130        )
    131        self.assertEqual(
    132            person.get('age'),
    133            this_year()-original_year_of_birth
    134        )
    135
    136        self.assertEqual(
    137            src.person.update_year_of_birth(
    138                person,
    139                new_year_of_birth
    140            ),
    141            dict(
    142                first_name=person.get('first_name'),
    143                last_name=person.get('last_name'),
    144                sex=person.get('sex'),
    145                age=this_year()-new_year_of_birth,
    146            )
    147        )
    148
    149
    150# Exceptions seen
    

    green.


  • I add a variable for the original age calculation

    111    def test_update_factory_person_year_of_birth(self):
    112        original_year_of_birth = 1580
    113        original_age = this_year() - original_year_of_birth
    114        new_year_of_birth = 1980
    
  • I use the variable in the first assertion for the age

    121        person = src.person.factory(
    122            first_name='john',
    123            last_name='smith',
    124            year_of_birth=original_year_of_birth,
    125        )
    126        self.assertEqual(
    127            person.get('age'),
    128            # this_year()-original_year_of_birth
    129            original_age
    130        )
    

    the test is still green.

  • I use the variable in the second assertion for the age

    127        with self.assertRaises(KeyError):
    128            person['year_of_birth']
    129        self.assertEqual(
    130            person.setdefault('year_of_birth', new_year_of_birth),
    131            new_year_of_birth
    132        )
    133        self.assertEqual(
    134            person.get('age'),
    135            # this_year()-original_year_of_birth
    136            original_age
    137        )
    

    still green.

  • I remove the commented lines

    111    def test_update_factory_person_year_of_birth(self):
    112        original_year_of_birth = 1580
    113        original_age = this_year() - original_year_of_birth
    114        new_year_of_birth = 1980
    115
    116        person = src.person.factory(
    117            first_name='john',
    118            last_name='smith',
    119            year_of_birth=original_year_of_birth,
    120        )
    121        self.assertEqual(
    122            person.get('age'),
    123            original_age
    124        )
    125
    126        with self.assertRaises(KeyError):
    127            person['year_of_birth']
    128        self.assertEqual(
    129            person.setdefault('year_of_birth', new_year_of_birth),
    130            new_year_of_birth
    131        )
    132        self.assertEqual(
    133            person.get('age'),
    134            original_age
    135        )
    136
    137        self.assertEqual(
    138            src.person.update_year_of_birth(
    139                person,
    140                new_year_of_birth
    141            ),
    142            dict(
    143                first_name=person.get('first_name'),
    144                last_name=person.get('last_name'),
    145                sex=person.get('sex'),
    146                age=this_year()-new_year_of_birth,
    147            )
    148        )
    149
    150
    151# Exceptions seen
    

I had to make a new person with the same first name, last name, sex and the new year of birth to change the year of birth. How would I solve this problem with a class?


test_person_tests

I want to write the solution without looking at the tests


RED: make it fail


  • I go back to the terminal that is running the tests

  • I close test_person.py in the editor

  • then I delete all the text in person.py, the terminal is my friend, and shows AttributeError

    FAILED ...test_factory_person_says_hello -
        AttributeError: module 'src.person' has
                        no attribute 'factory'
    FAILED ...test_factory_w_keyword_arguments -
        AttributeError: module 'src.person' has
                        no attribute 'factory'
    FAILED ...test_factory_w_optional_arguments -
        AttributeError: module 'src.person' has
                        no attribute 'factory'
    

    because there is nothing in person.py with the name factory


GREEN: make it pass


  • I add the name

    1factory
    

    the terminal is my friend, and shows NameError

    NameError: name 'factory' is not defined
    

    because I have not told Python what factory means

  • I point it to None to define it

    1factory = None
    

    the terminal is my friend, and shows TypeError

    TypeError: 'NoneType' object is not callable
    

    because factory points to None and I cannot call None like a function

  • I make factory a function to make it callable

    1def factory():
    2    return None
    

    the terminal is my friend, and shows TypeError

    TypeError: factory() got
               an unexpected keyword argument 'first_name'
    

    because the test called the factory function with a keyword argument (first_name) that is not in the function definition

  • I add first_name to the function definition

    1def factory(first_name):
    2    return None
    

    the terminal is my friend, and shows TypeError

    TypeError: factory() got
               an unexpected keyword argument 'year_of_birth'
    

    because the test called the factory function with a keyword argument (year_of_birth) that is not in the function definition

  • I add year_of_birth to the function definition

    1def factory(first_name, year_of_birth):
    2    return None
    

    the terminal is my friend, and shows AssertionError

    AssertionError: None != {'first_name': Y,
                             'last_name': 'doe',
                             'sex': 'M', 'age': X}
    

    because the assertion expects a dictionary and the factory function returns None

  • I copy and paste the dictionary from the terminal to make the function return a dictionary instead of None

    1def factory(first_name, year_of_birth):
    2    return {
    3        'first_name': 'john',
    4        'last_name': 'doe',
    5        'sex': 'M',
    6        'age': 55,
    7    }
    

    the terminal is my friend, and shows AssertionError

    AssertionError:
        {'first_name': A, 'last_name': 'doe',
         'sex': 'M', 'age': Y}
     != {'first_name': Z, 'last_name': 'doe',
         'sex': 'M', 'age': X}
    

    the values of the age and first_name keys change randomly

  • I use the first_name input parameter in the return statement

    1def factory(first_name, year_of_birth):
    2    return {
    3        # 'first_name': 'john',
    4        'first_name': first_name,
    5        'last_name': 'doe',
    6        'sex': 'M',
    7        'age': 55,
    8    }
    

    the first name matches and if the ages are different, the terminal is my friend, and shows AssertionError

    AssertionError:
        {'first_name': Z, 'last_name': 'doe',
         'sex': 'M', 'age': Y}
     != {'first_name': Z, 'last_name': 'doe',
         'sex': 'M', 'age': X}
    

    and TypeError

    TypeError: factory() got
               an unexpected keyword argument 'last_name'.
               Did you mean 'first_name'?
    
  • I use the year_of_birth input parameter in the return statement for the value of age

    1def factory(first_name, year_of_birth):
    2    return {
    3        # 'first_name': 'john',
    4        'first_name': first_name,
    5        'last_name': 'doe',
    6        'sex': 'M',
    7        # 'age': 55,
    8        'age': year_of_birth,
    9    }
    

    the terminal is my friend, and shows AssertionError

    AssertionError:
        {'first_name': Y, 'last_name': 'doe',
         'sex': 'M', 'age': ABCD}
     != {'first_name': Y, 'last_name': 'doe',
         'sex': 'M', 'age': X}
    

    because the factory function returned 4 digits (a year) as the value for the age key, and the assertion expects the difference between that value and the current year

  • I add an import statement for the datetime module at the top of the file

    1import datetime
    2
    3
    4def factory(first_name, year_of_birth):
    

    the terminal still shows AssertionError

  • I use the datetime module to get the current year for the age calculation

     4def factory(first_name, year_of_birth):
     5    return {
     6        # 'first_name': 'john',
     7        'first_name': first_name,
     8        'last_name': 'doe',
     9        'sex': 'M',
    10        # 'age': 55,
    11        # 'age': year_of_birth,
    12        'age': (
    13            datetime.datetime.today().year
    14           -year_of_birth
    15        ),
    16    }
    

    the terminal is my friend, and shows TypeError

    TypeError: factory() got
               an unexpected keyword argument 'last_name'.
               Did you mean 'first_name'?
    

    because the test called the factory function with a keyword argument (last_name) that is not in the function definition

  • I add a new input parameter to the function

    4def factory(
    5        first_name, year_of_birth,
    6        last_name,
    7    ):
    

    the terminal is my friend, and shows TypeError

    TypeError: factory() missing 1
               required positional argument: 'last_name'
    

    because the test called the function with another argument and Python took that argument as a positional argument for last_name

  • I add a default value for last_name so Python does not take it is a positional argument when a name is not given

    4def factory(
    5        first_name, year_of_birth,
    6        last_name=None,
    7    ):
    

    the terminal is my friend, and shows TypeError

    TypeError: factory() got
               an unexpected keyword argument 'sex'
    

    because the test called the factory function with a keyword argument (sex) that is not in the function definition

  • I add the name to the definition of the function

    4def factory(
    5        first_name, year_of_birth,
    6        last_name=None, sex,
    7    ):
    

    the terminal is my friend, and shows SyntaxError

    SyntaxError: parameter without a default follows
                 parameter with a default
    

    because parameters without default values must come before parameters with default values

  • I add a default value for sex

    1def factory(
    2        first_name, year_of_birth,
    3        last_name=None, sex=None,
    4    ):
    

    the terminal is my friend, and shows AssertionError first_name and age match. If the last names are different, the terminal is my friend, and shows AssertionError

    AssertionError:
        {'first_name': C, 'last_name': B, 'sex': Z, 'age': X}
     != {'first_name': C, 'last_name': A, 'sex': Y, 'age': X}
    

    and AttributeError

    AttributeError: module 'src.person'
                    has no attribute 'say_hello'
    
  • I use the sex input parameter in the return statement

     4def factory(
     5        first_name, year_of_birth,
     6        last_name=None, sex=None,
     7    ):
     8    return {
     9        # 'first_name': 'john',
    10        'first_name': first_name,
    11        'last_name': 'doe',
    12        # 'sex': 'M',
    13        'sex': sex,
    14        # 'age': 55,
    15        # 'age': year_of_birth,
    16        'age': (
    17            datetime.datetime.today().year
    18           -year_of_birth
    19        ),
    20    }
    

    the terminal is my friend, and shows AssertionError

    AssertionError:
        {'first_name': Y, 'last_name': 'doe',
         'sex': None, 'age': X}
     != {'first_name': Y, 'last_name': 'doe',
         'sex': 'M', 'age': X}
    

    because the assertion expects 'M' as the value of sex and the function returns None which is its default value

  • I change the default value of sex to 'M'

    4def factory(
    5        first_name, year_of_birth,
    6        last_name=None, sex='M',
    7    ):
    

    I use ctrl+s (Windows/Linux) or command+s (MacOS) to run the test a few times and if the last_name is not 'doe', the terminal is my friend, and shows AssertionError

    AssertionError:
        {'first_name': A, 'last_name': 'doe', 'sex': Y, 'age': X}
     != {'first_name': A, 'last_name': Z, 'sex': Y, 'age': X}
    

    because the last_name value is different between the two dictionaries. It still shows AttributeError

    AttributeError: module 'src.person'
                    has no attribute 'say_hello'
    
  • I use the last_name input parameter in the return statement

     4def factory(
     5        first_name, year_of_birth,
     6        last_name=None, sex='M',
     7    ):
     8    return {
     9        # 'first_name': 'john',
    10        'first_name': first_name,
    11        # 'last_name': 'doe',
    12        'last_name': last_name,
    13        # 'sex': 'M',
    14        'sex': sex,
    15        # 'age': 55,
    16        # 'age': year_of_birth,
    17        'age': (
    18            datetime.datetime.today().year
    19           -year_of_birth
    20        ),
    21    }
    

    the terminal is my friend, and shows AssertionError

    AssertionError:
        {'first_name': Z, 'last_name': None, 'sex': Y, 'age': X}
     != {'first_name': Z, 'last_name': 'doe', 'sex': Y, 'age': X}
    

    because the assertion expects 'doe' as the value of last_name and the function returns None which is its default value

  • I change the default value for last_name to match the expectation

    1def factory(
    2        first_name, year_of_birth,
    3        last_name='doe', sex='M',
    4    ):
    

    the terminal is my friend, and shows AttributeError

    AttributeError: module 'src.person'
                    has no attribute 'say_hello'
    

    because I do not have a definition for say_hello

  • I add say_hello

     1import datetime
     2
     3
     4say_hello
     5
     6
     7def factory(
     8        first_name, year_of_birth,
     9        last_name='doe', sex='M',
    10    ):
    

    the terminal is my friend, and shows NameError

    NameError: name 'say_hello' is not defined
    
  • I point it to None to define it

     1import datetime
     2
     3
     4say_hello = None
     5
     6
     7def factory(
     8        first_name, year_of_birth,
     9        last_name='doe', sex='M',
    10    ):
    

    the terminal is my friend, and shows TypeError

    TypeError: 'NoneType' object is not callable
    

    because say_hello points to None and I cannot call None like a function

  • I make say_hello a function to make it callable

     1import datetime
     2
     3
     4def say_hello():
     5    return None
     6
     7
     8def factory(
     9        first_name, year_of_birth,
    10        last_name='doe', sex='M',
    11    ):
    

    the terminal is my friend, and shows TypeError

    TypeError: say_hello() takes 0 positional arguments
               but 1 was given
    

    because the function definition for say_hello does not allow calling it with inputs (the parentheses are empty) and the test sends input.

  • I add a name to the function definition

    5def say_hello(argument):
    6    return None
    

    the terminal is my friend, and shows AssertionError

    AssertionError: None != 'Hi, my name is Z Y and I am Z'
    

    because the test expects a string and the say_hello function returns None

  • I copy the string from the terminal and paste it to replace the return statement

    4def say_hello(argument):
    5    return 'Hi, my name is jane doe and I am 66'
    

    the terminal is my friend, and shows AssertionError

    AssertionError:
        'Hi, my name is Z Y and I am X'
     != 'Hi, my name is A B and I am C'
    

    I use ctrl+s (Windows/Linux) or command+s (MacOS) to run the test a few times and the names and age change

  • I return the input to compare it with what the test expects

    4def say_hello(argument):
    5    return argument
    6    return 'Hi, my name is jane doe and I am 66'
    

    the terminal shows AssertionError

    the test sends a dictionary as input and expects a string as output, and the string uses the values of the first_name, last_name and age keys in it

  • I return an f-string with the values of the first_name, last_name and age keys from the dictionary

     4def say_hello(argument):
     5    return (
     6        f'Hi, my name is {argument.get("first_name")}'
     7        f' {argument.get("last_name")}'
     8        f' and I am {argument.get("age")}'
     9    )
    10    return argument
    11    return 'Hi, my name is jane doe and I am 66'
    

    the test passes. Okay!


REFACTOR: make it better


  • I use the Rename Symbol feature of the Integrated Development Environment (IDE) to change argument to make it clearer

     4def say_hello(a_dictionary):
     5    return (
     6        f'Hi, my name is {a_dictionary.get("first_name")}'
     7        f' {a_dictionary.get("last_name")}'
     8        f' and I am {a_dictionary.get("age")}'
     9    )
    10    return a_dictionary
    11    return 'Hi, my name is jane doe and I am 66'
    
  • I remove the other return statements

     1import datetime
     2
     3
     4def say_hello(a_dictionary):
     5    return (
     6        f'Hi, my name is {a_dictionary.get("first_name")}'
     7        f' {a_dictionary.get("last_name")}'
     8        f' and I am {a_dictionary.get("age")}'
     9    )
    10
    11
    12def factory(
    13        first_name, year_of_birth,
    14        last_name='doe', sex='M',
    15    ):
    
  • I remove the commented lines from the factory function

    12def factory(
    13        first_name, year_of_birth,
    14        last_name='doe', sex='M',
    15    ):
    16    return {
    17        'first_name': first_name,
    18        'last_name': last_name,
    19        'sex': sex,
    20        'age': (
    21            datetime.datetime.today().year
    22           -year_of_birth
    23        ),
    24    }
    

    This factory function only has two parameters with default values (last_name and sex)

      def factory(
              first_name, year_of_birth,
              last_name='doe', sex='M',
          ):
    

    the first solution had three parameters with default values (last_name, sex and year_of_birth)

    def factory(
            first_name, last_name='doe',
            sex='M', year_of_birth=None,
        ):
    

    I can learn new things from repetition.

  • I add a git commit message in the other terminal

    git commit -am 'refactor factory and say_hello'
    

    the terminal shows a summary of the changes then goes back to the command line.

  • I go back to the terminal that is running the tests


close the project

  • I close person.py in the editor

  • I click in the terminal where the tests are running, then use q on the keyboard to leave the tests. The terminal goes back to the command line.

  • I change directory to the parent of person

    cd ..
    

    the terminal shows

    ...\pumping_python
    

    I am back in the pumping_python directory


review

I ran tests to make

I also saw these Exceptions


code from the chapter

Do you want to see all the CODE I typed in this chapter?


what is next?

you know:

Would you like to see another way to make a person?


rate pumping python

If this has been a 7 star experience for you, please CLICK HERE to leave a 5 star review of pumping python. It helps other people get into the book too.